loaders.zig is a production-oriented Zig library for animated spinners, progress bars, and multi-progress terminal UIs. It is designed for low overhead, clean output, and cross-platform terminal behavior on Linux, Windows, and macOS.
Tip
loaders.zig uses tint.zig internally for color support — ANSI 4-bit, 256-color, RGB/TrueColor, HEX, HSL, HSV, CMYK, and 140+ named colors. You can also pass raw ANSI escape sequences directly.
| Requirement | Version |
|---|---|
| Zig | 0.16.0 |
| OS | Windows, Linux, macOS |
zig fetch --save https://github.com/muhammad-fiaz/loaders.zig/archive/refs/tags/0.0.5.tar.gzzig fetch --save git+https://github.com/muhammad-fiaz/loaders.zig.gitgit clone https://github.com/muhammad-fiaz/loaders.zig.git
cd loaders.zig
zig buildImportant
After installing, wire the dependency into your build.zig:
const loaders = b.dependency("loaders", .{});
exe.root_module.addImport("loaders", loaders.module("loaders"));Tip
Use zig fetch --save for the automatic flow. It resolves the hash and writes it into build.zig.zon for you.
const std = @import("std");
const loaders = @import("loaders");
pub fn main() !void {
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const allocator = std.heap.page_allocator;
var bar = try loaders.ProgressBar.init(allocator, io, .{
.total = 100,
.style = .{ .filled = "#", .empty = "-" },
.template = "{bar} {percent}%",
.text = "Processing",
});
defer bar.deinit();
var i: u64 = 0;
while (i <= 100) : (i += 1) {
bar.setProgress(i);
loaders.sleepMs(io, 30);
}
bar.finish(.{ .newline = true });
}var sp = try loaders.Spinner.init(allocator, io, .{
.frames = &.{ "|", "/", "-", "\\" },
.template = "{frame} {text}",
.text = "Loading",
});
defer sp.deinit();
try sp.start();
loaders.sleepMs(io, 2000);
sp.stop(.{ .final_text = "Done!", .newline = true });Caution
On Windows, Unicode characters (Braille, emoji) require UTF-8 console encoding. loaders.zig automatically enables UTF-8 mode on Windows.
var mb = try loaders.MultiBar.init(allocator, io, .{});
defer mb.deinit();
_ = try mb.addBar(.{
.total = 100,
.style = .{ .filled = "#", .empty = "-" },
.template = "Task A: {bar} {percent}%",
});
_ = try mb.addBar(.{
.total = 100,
.style = .{ .filled = "=", .empty = " " },
.template = "Task B: {bar} {percent}%",
});
try mb.run();
// ... update bars ...
mb.finishAll(.{ .newline = true });| Feature | Description |
|---|---|
| Custom Bar Styles | Override fill, empty, head, and bracket characters |
| Custom Spinner Frames | Provide any frame sequence (Braille, emoji, ASCII, etc.) |
| ETA & Speed | Real-time estimated time remaining and throughput via formatters |
| Dynamic Messages | Update text and color dynamically based on task phase or state |
| Multi-Progress | Sequential and parallel multi-bar rendering |
| Batch Runner | Process items with per-item and overall progress bars |
| Step Sequences | Ordered multi-step pipelines with spinner or bar per step |
| Thread Modes | .none (manual), .auto (background thread), .external (caller-driven) |
| Pause/Resume | Freeze and resume clocks and rendering |
| Callbacks | on_tick, on_finish, on_pause, on_resume hooks |
| Runtime Swaps | Change style, frames, template, text, color at runtime |
| Color | tint.zig — ANSI 4-bit, 256-color, RGB/TrueColor, HEX, HSL, HSV, CMYK, named colors |
| Template Engine | {bar}, {frame}, {percent}, {count}, {elapsed}, {eta}, {speed}, {color}, {reset} |
| Windows UTF-8 | Automatic console code page setup for Unicode characters |
Colors use tint.zig internally — pass color.toFg() or use convenience functions:
// tint.zig color functions
.color = loaders.fg(.{ .ansi4 = .green }) // ANSI 4-bit green
.color = loaders.makeRgb(34, 197, 94).toFg() // RGB (TrueColor)
.color = loaders.makeHex(0x22C55E).toFg() // HEX color
.color = loaders.makeAnsi256(129).toFg() // ANSI 256-color
.color = loaders.fg(.{ .named = .red }) // CSS named color
// Raw ANSI strings still work
.color = "\x1b[32m" // green
.color = "\x1b[38;2;0;255;0m" // green RGB
// No color
.color = nullColors can be updated at runtime with bar.setColor(...) / sp.setColor(...).
// Create
var bar = try loaders.ProgressBar.init(allocator, io, .{
.total = 100,
.style = .{ .filled = "#", .empty = "-" },
.template = "{bar} {percent}%",
.text = "Processing",
.color = loaders.fg(.{ .ansi4 = .green }), // green via tint.zig
.formatters = .{
.elapsed = formatElapsed,
.eta = formatEta,
.speed = formatSpeed,
},
});
defer bar.deinit();
// Control
bar.setProgress(50); // Set absolute value
bar.pause(); // Pause clock
bar.continue_(); // Resume from pause
bar.forceRedraw(); // Force immediate render
bar.finish(.{ .newline = true });
bar.fail("Network error");
// Update at runtime
bar.setText("new text");
bar.setPrefix(">");
bar.setSuffix("<");
bar.setColor(loaders.fg(.{ .ansi4 = .red }));
bar.setStyle(.{ .filled = "=", .empty = " ", .head = ">" });
try bar.setTemplate("{bar} {elapsed}");
// State
const s = bar.state(); // ProgressState
bar.getStatus(); // .pending | .running | .paused | .finished | .failed// Create
var sp = try loaders.Spinner.init(allocator, io, .{
.frames = &.{ "|", "/", "-", "\\" },
.template = "{frame} {text}",
.text = "Loading",
.color = loaders.fg(.{ .ansi4 = .blue }), // blue via tint.zig
.thread_mode = .auto,
});
defer sp.deinit();
// Control
try sp.start();
sp.tickFrame(); // Advance one frame
sp.setProgress(5); // Set absolute frame index
sp.getCurrent(); // Get current frame index
sp.stop(.{ .final_text = "Done!", .newline = true });
// Update at runtime
sp.setText("new text");
sp.setColor(loaders.fg(.{ .ansi4 = .red }));
sp.setFrames(&.{ ".", "..", "..." });
try sp.setTemplate("{frame} {text}");
// State
const s = sp.state(); // SpinnerState
sp.getStatus(); // .pending | .running | .finished | .failedvar mb = try loaders.MultiBar.init(allocator, io, .{
.mode = .sequential, // .sequential | .parallel
});
defer mb.deinit();
_ = try mb.addBar(.{ ... });
_ = try mb.addSpinner(.{ ... });
try mb.run();
mb.finishAll(.{ .newline = true });var batch = try loaders.BatchRunner.init(allocator, io, .{
.mode = .sequential,
.show_overall_bar = true,
.overall_bar_config = .{
.total = 5,
.style = .{ .filled = "#", .empty = "-" },
.template = "Overall: {bar} {count}",
},
});
defer batch.deinit();
const items = [_]u32{ 1, 2, 3, 4, 5 };
try batch.run(u32, &items, processItem);In parallel mode, use itemBar() to drive per-item progress from the worker:
const WorkerCtx = struct {
batch: *loaders.BatchRunner,
};
fn downloadWorker(item: DownloadItem, ctx: ?*anyopaque) void {
const c: *WorkerCtx = @ptrCast(@alignCast(ctx orelse return));
const bar = c.batch.itemBar() orelse return;
var downloaded: u64 = 0;
while (downloaded < item.size) : (downloaded += 1) {
bar.setProgress(downloaded);
loaders.sleepMs(g_threaded.io(), 2);
}
bar.setProgress(item.size);
}var seq = try loaders.StepSequence.init(allocator, io, .{});
defer seq.deinit();
_ = try seq.addStep(.{ .name = "Install", .kind = .{ .spinner = .{
.frames = &.{ ".", "..", "..." },
.template = "{frame} Installing...",
} } });
try seq.startStep(0);
// ... do work ...
seq.completeStep(0, .{});| Token | Description |
|---|---|
{bar} |
Rendered bar track (filled + empty characters) |
{frame} |
Current spinner animation frame |
{percent} |
Progress percentage (e.g. 50.0) |
{count} |
Current/total count (e.g. 50/100) |
{elapsed} |
Elapsed time (requires formatters.elapsed) |
{eta} |
Estimated time remaining (requires formatters.eta) |
{speed} |
Throughput rate (requires formatters.speed) |
{prefix} |
Optional prefix text |
{suffix} |
Optional suffix text |
{text} |
Optional display text |
{color} |
Raw ANSI color escape sequence |
{reset} |
ANSI reset sequence (\x1b[0m) |
Warning
If {elapsed}, {eta}, or {speed} are in the template but no formatter is provided, init() returns error.MissingFormatter.
For {elapsed}, {eta}, and {speed} tokens, provide formatter functions:
fn formatElapsed(ns: u64, buf: []u8) []const u8 {
return loaders.formatNs(buf, ns); // "MM:SS" or "HH:MM:SS"
}
fn formatEta(ns: u64, buf: []u8) []const u8 {
return loaders.formatNs(buf, ns);
}
fn formatSpeed(per_sec: f64, buf: []u8) []const u8 {
return loaders.formatRate(buf, per_sec); // "123.4/s"
}
var bar = try loaders.ProgressBar.init(allocator, io, .{
.total = 200,
.style = .{ .filled = "=", .empty = " ", .head = ">" },
.template = "{bar} {percent}% | Elapsed: {elapsed} ETA: {eta} | {speed}",
.width = 30,
.formatters = .{
.elapsed = formatElapsed,
.eta = formatEta,
.speed = formatSpeed,
},
});All 40 examples live in examples/:
zig build examples # Build all examples
zig build run-all-examples # Run all examples sequentially
# Run individual examples
zig build run-basic_bar
zig build run-basic_spinner
zig build run-custom_ascii_bar
zig build run-custom_bracket_bar
zig build run-block_bar
zig build run-indeterminate
zig build run-template_with_eta_speed
zig build run-runtime_style_swap
zig build run-runtime_frame_swap
zig build run-manual_tick
zig build run-external_thread
zig build run-auto_thread
zig build run-multi_bar_sequential
zig build run-multi_bar_parallel
zig build run-batch_sequential
zig build run-batch_parallel_downloads
zig build run-batch_dynamic_messages
zig build run-step_sequence_basic
zig build run-step_runall
zig build run-custom_colors_rgb
zig build run-custom_colors_hex
zig build run-custom_colors_dynamic_gradient
zig build run-pause_resume
zig build run-text_updates
zig build run-dynamic_messages
zig build run-dynamic_spinner_messages
zig build run-spinner_looping_messages
zig build run-spinner_conditional_messages
zig build run-infinite_spinner
zig build run-infinite_progress_bar
zig build run-clear_on_finish
zig build run-fail_and_status
zig build run-callback_hooks
zig build run-state_accessor
zig build run-starting_value
zig build run-indeterminate_timeout
zig build run-progress_bar_unicode
zig build run-progress_bar_countdown
zig build run-spinner_braille
zig build run-progress_bar_countdown_etazig build test # Run unit tests
zig build examples # Build all examples
zig build docs # Generate library documentationMIT License - see LICENSE for details.
- Repository: https://github.com/muhammad-fiaz/loaders.zig
- Documentation: https://muhammad-fiaz.github.io/loaders.zig/
