Loaders
A fully-featured, customisable progress bar and loading indicator library for Rust CLI applications, implemented with only the standard library.
Features
- Progress bars with known or unknown totals.
- Spinners with built-in frame presets.
- Multi-progress rendering for concurrent work.
- Iterator integration with automatic length detection.
- Human-readable ETA, elapsed time, and rate values.
- Byte formatting and byte throughput templates.
- Custom templates parsed once and reused.
- Custom template keys for application-specific labels.
- ANSI colors with
NO_COLORsupport. - Built-in themes for common terminal styles.
- Draw throttling by position delta and redraw rate.
- Hidden draw targets for tests and quiet mode.
- Custom
Writetargets for captures and logs. - Thread-safe cloned progress bars.
- Background steady ticking for spinners.
- Reusable
ProgressCharsfor custom progress bar shapes. - Status-aware templates with
{status},{remaining},{ratio}, and compact counts. - Spinner helpers for success, failure, warning, and informational endings.
- 27 built-in spinner and loading frame presets.
Install
cargo add loaders
[dependencies]
loaders = "0.0.0"
Four-Line Progress Bar
#![allow(unused)] fn main() { use loaders::ProgressBar; let pb = ProgressBar::new(100); pb.inc(50); pb.finish_with_message("done"); }
Guides
- Getting Started
- API Reference
- Progress Bars
- Spinners
- Multi Progress
- Styling
- Themes
- Advanced Usage
- Examples
Getting Started
loaders gives you three main building blocks: ProgressBar for measured work,
Spinner for indeterminate work, and MultiProgress for several bars rendered as one
terminal block.
Add the Dependency
[dependencies]
loaders = "0.0.0"
First Progress Bar
use loaders::ProgressBar; fn main() { let pb = ProgressBar::new(3); for _ in 0..3 { pb.inc(1); } pb.finish_with_message("done"); }
First Spinner
use loaders::{Spinner, spinner::frames::DOTS}; use std::time::Duration; fn main() { let spinner = Spinner::new_with_interval(&DOTS, Duration::from_millis(80)); spinner.start_with_message("connecting"); spinner.stop_with_message("connected"); }
Iterator Usage
use loaders::ProgressIterator; fn main() { let sum: u64 = (0..100u64).progress().sum(); println!("{sum}"); }
Which Type Should I Use?
Use ProgressBar when there is a count, byte total, or step count. Use Spinner
when work is happening but a total is unknown. Use MultiProgress when multiple
threads or phases should be visible together.
Draw Targets and Hidden Bars
Progress output defaults to stderr. DrawTarget::hidden() suppresses terminal output and
is the recommended target in tests. Non-TTY streams receive newline snapshots instead of
ANSI cursor movement, which keeps pipes and logs readable. CI and NO_COLOR environments
avoid color escape sequences.
API Reference
| Item | Signature | Description | Guide |
|---|---|---|---|
ProgressBar | struct ProgressBar | Thread-safe progress bar handle. | Progress Bars |
ProgressBarBuilder | struct ProgressBarBuilder | Builder for length, style, target, template, and throttling. | Progress Bars |
ProgressBar::new | fn new(len: u64) -> ProgressBar | Creates a measured bar. | Progress Bars |
ProgressBar::new_spinner | fn new_spinner() -> ProgressBar | Creates an indeterminate bar. | Spinners |
ProgressBar::hidden | fn hidden() -> ProgressBar | Creates a silent bar for tests. | Advanced |
ProgressBar::with_draw_target | fn with_draw_target(len, target) -> ProgressBar | Uses a custom draw target. | Advanced |
ProgressBar::with_style | fn with_style(len, style) -> ProgressBar | Creates a bar with a custom style. | Styling |
ProgressBar::with_template | fn with_template(len, template) -> Result<ProgressBar, TemplateError> | Creates a bar from a custom template. | Styling |
ProgressBar::builder | fn builder() -> ProgressBarBuilder | Starts builder configuration. | Progress Bars |
ProgressBarBuilder::template | fn template(self, template) -> Result<Self, TemplateError> | Sets a custom template while building. | Styling |
inc / inc_by | fn inc(&self, delta: u64) | Advances position. | Progress Bars |
set_position | fn set_position(&self, pos: u64) | Sets absolute position. | Progress Bars |
set_length | fn set_length(&self, len: u64) | Sets total length. | Progress Bars |
set_length_unknown | fn set_length_unknown(&self) | Clears the known total. | Progress Bars |
set_message | fn set_message(&self, msg) | Sets {msg}. | Styling |
set_prefix | fn set_prefix(&self, prefix) | Sets {prefix}. | Styling |
set_postfix | fn set_postfix(&self, postfix) | Sets {postfix}. | Styling |
tick | fn tick(&self) | Advances spinner frame. | Spinners |
finish | fn finish(&self) | Completes the bar. | Progress Bars |
finish_with_message | fn finish_with_message(&self, msg) | Completes with final message. | Progress Bars |
finish_with_symbol | fn finish_with_symbol(&self, symbol, msg) | Completes with a fixed prefix marker. | Progress Bars |
finish_and_clear | fn finish_and_clear(&self) | Completes and clears output. | Progress Bars |
abandon | fn abandon(&self) | Stops without finished state. | Progress Bars |
reset | fn reset(&self) | Reuses the bar from zero. | Progress Bars |
set_style | fn set_style(&self, style) | Replaces visual style. | Styling |
set_template | fn set_template(&self, template) -> Result<(), TemplateError> | Replaces style from a template. | Styling |
set_draw_delta | fn set_draw_delta(&self, delta) | Redraws after enough progress. | Advanced |
set_draw_rate | fn set_draw_rate(&self, rate) | Caps redraws per second. | Advanced |
enable_steady_tick | fn enable_steady_tick(&self, interval) | Starts background ticking. | Spinners |
println | fn println(&self, msg: &str) | Prints above the bar. | Progress Bars |
wrap_iter | fn wrap_iter<I>(&self, iter: I) | Uses a bar for an iterator. | Examples |
fraction | fn fraction(&self) -> f64 | Returns completion ratio. | Progress Bars |
percent | fn percent(&self) -> f64 | Returns completion percent. | Progress Bars |
remaining | fn remaining(&self) -> Option<u64> | Returns remaining work. | Progress Bars |
is_finished | fn is_finished(&self) -> bool | Returns completion state. | Progress Bars |
is_abandoned | fn is_abandoned(&self) -> bool | Returns abandonment state. | Progress Bars |
Spinner | struct Spinner | Ergonomic spinner wrapper. | Spinners |
Spinner::success | fn success(&self, msg) | Stops with a success marker. | Spinners |
Spinner::failure | fn failure(&self, msg) | Stops with a failure marker. | Spinners |
Spinner::warning | fn warning(&self, msg) | Stops with a warning marker. | Spinners |
Spinner::info | fn info(&self, msg) | Stops with an informational marker. | Spinners |
MultiProgress | struct MultiProgress | Shared block for many bars. | Multi Progress |
ProgressStyle | struct ProgressStyle | Template, chars, colors, keys. | Styling |
ProgressChars | struct ProgressChars | Reusable fill, head, and empty characters. | Styling |
Theme | enum Theme | Built-in style presets. | Themes |
Color | enum Color | ANSI color value. | Styling |
ColorSpec | struct ColorSpec | ANSI text attributes. | Styling |
DrawTarget | enum DrawTarget | Stdout, stderr, hidden, or writer. | Advanced |
terminal::detect::terminal_width_for_fd | fn terminal_width_for_fd(fd: i32) -> usize | Detects width for stdout/stderr with fallback. | Advanced |
terminal::detect::terminal_height_for_fd | fn terminal_height_for_fd(fd: i32) -> usize | Detects height for stdout/stderr with fallback. | Advanced |
terminal::detect::supports_interactive_output | fn supports_interactive_output(fd: i32) -> bool | Checks interactive cursor-update support for a stream. | Advanced |
ProgressIterator | trait ProgressIterator | Adds .progress() methods. | Examples |
format_bytes | fn format_bytes(u64) -> String | Human byte count. | Styling |
format_duration | fn format_duration(Duration) -> String | Human duration. | Styling |
Progress Bars
ProgressBar is cloneable and all mutation methods take &self, so a bar can be shared
between threads or moved into worker closures.
Construction
#![allow(unused)] fn main() { use loaders::{DrawTarget, ProgressBar, Theme}; let plain = ProgressBar::new(100); let hidden = ProgressBar::hidden(); let targeted = ProgressBar::with_draw_target(100, DrawTarget::stderr()); let custom = ProgressBar::with_style(100, Theme::Unicode.style()); let templated = ProgressBar::with_template(100, "{prefix} [{bar:20}] {percent}%")?; let built = ProgressBar::builder() .length(100) .template("{prefix} [{bar:20}] {remaining} left")? .message("loading") .prefix("job") .draw_delta(2) .draw_rate(30) .build(); Ok::<(), loaders::bar::template::TemplateError>(()) }
Mutating Progress
Use inc or inc_by for relative progress and set_position for absolute progress.
set_length can be called later when a total becomes known. set_length_unknown clears
the total and makes the bar indeterminate again.
#![allow(unused)] fn main() { let pb = loaders::ProgressBar::new(10); pb.set_prefix("compile"); pb.set_message("parsing"); pb.inc(3); pb.set_position(7); pb.set_postfix("fast path"); pb.set_length_unknown(); }
Width Control
Use {bar:N} for a fixed bar width and {wide_bar} for terminal-adaptive width.
{wide_bar} recalculates on each draw and fills the remaining width in the full
rendered template.
#![allow(unused)] fn main() { let fixed = loaders::ProgressBar::with_template(100, "[{bar:20}] {percent}%")?; let adaptive = loaders::ProgressBar::with_template(100, "[{wide_bar}] {percent}%")?; Ok::<(), loaders::bar::template::TemplateError>(()) }
Reading Progress State
position, length, remaining, fraction, and percent make progress state visible
for summaries and application logic.
#![allow(unused)] fn main() { let pb = loaders::ProgressBar::hidden(); pb.set_length(10); pb.inc(4); assert_eq!(pb.remaining(), Some(6)); assert_eq!(pb.fraction(), 0.4); assert_eq!(pb.percent(), 40.0); }
Draw Delta and Draw Rate
draw_delta skips redraws until enough position changes have accumulated. It is useful
for hot loops. draw_rate limits redraws per second and is useful when progress changes
very frequently. Set draw_rate(0) for unlimited redraws.
Interactive terminals redraw in place on the same line. Non-interactive outputs (pipes, files, CI logs, custom non-TTY writers) fall back to newline snapshots.
Steady Tick
enable_steady_tick spawns a background thread that calls tick() until the bar finishes
or disable_steady_tick() is called.
#![allow(unused)] fn main() { use std::time::Duration; let pb = loaders::ProgressBar::new_spinner(); pb.enable_steady_tick(Duration::from_millis(80)); pb.set_message("waiting"); pb.disable_steady_tick(); pb.finish(); }
Finishing
finish completes the bar. finish_with_message sets a final message first.
finish_with_symbol sets a fixed marker prefix and final message. finish_and_clear
removes the line. abandon leaves the bar unfinished, which is useful for cancelled work
and can be inspected with is_abandoned.
Reset and Reuse
reset clears position, completion state, spinner frame index, and timing. It keeps the
same style, length, target, prefix, and message so repeated phases can reuse one handle.
Spinners
A spinner is a progress bar without a length. You can use ProgressBar::new_spinner() or
the Spinner wrapper when you prefer spinner-specific method names.
Presets
loaders ships with 27 built-in loading frame presets:
| Name | Frames |
|---|---|
dots | braille dot cycle |
dots2 | heavy braille dot cycle |
dots3 | smooth braille dot cycle |
line | `- \ |
pipe | box pipe cycle |
star | star pulse |
bounce | minimal bounce |
arrows | directional arrows |
clock | clock faces |
earth | globe rotation |
moon | moon phases |
runner | walking/running frames |
pong | moving paddle frames |
shark | swimming text frames |
weather | weather icons |
christmas | tree frames |
toggle | filled/empty square |
square_corners | rotating square corners |
binary | 0 1 10 11 |
meter | ▱▱▱ ▰▱▱ ▰▰▱ ▰▰▰ |
quadrants | ◐ ◓ ◑ ◒ |
triangles | ◢ ◣ ◤ ◥ |
circle | ○ ◔ ◐ ◕ ● |
box_bounce | moving block |
pulse | . .. ... .. |
heart | ♡ ♥ |
progress_blocks | ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ |
Custom Frames
#![allow(unused)] fn main() { use loaders::{ProgressBar, ProgressStyle}; let pb = ProgressBar::new_spinner(); pb.set_style(ProgressStyle::default_spinner().tick_strings(&[".", "o", "O", "o"])); pb.tick(); }
Success and Failure Symbols
#![allow(unused)] fn main() { use loaders::{Spinner, spinner::frames::DOTS}; let spinner = Spinner::with_message(&DOTS, "uploading"); spinner.stop_with_symbol("ok", "uploaded"); }
Convenience helpers provide portable text markers:
#![allow(unused)] fn main() { use loaders::{Spinner, spinner::frames::METER}; let spinner = Spinner::with_message(&METER, "checking"); spinner.success("ready"); }
Use failure, warning, and info for the other common terminal outcomes.
Custom Message and Padding
Spinners use the same template engine as progress bars, so message fields can be padded
or truncated with width specs such as {msg:>20} and {prefix:^10}.
Tick Intervals
Fast terminal spinners usually feel good at 60-120ms. Longer operations with expensive
rendered templates can use 120-250ms. Background ticking is optional; manual tick() is
better when each loop iteration naturally represents progress.
Multi Progress
MultiProgress owns a single renderer and stores bars in draw order. Child bars are
updated through their normal ProgressBar handles, and join refreshes the block until
every bar is finished or abandoned.
Thread Safety
ProgressBar and MultiProgress use Arc<Mutex<_>> internally. Cloning either handle
shares the same state, which makes them suitable for worker threads.
#![allow(unused)] fn main() { use loaders::{MultiProgress, ProgressBar}; use std::thread; let multi = MultiProgress::new(); let pb = multi.add(ProgressBar::new(10)); let handle = thread::spawn(move || { for _ in 0..10 { pb.inc(1); } pb.finish(); }); multi.join(); let _ = handle.join(); }
Ordering
Use add to append, insert for an index, insert_before and insert_after for relative
placement, and remove when a bar should leave the block.
Messages Above the Block
multi.println("message") prints without corrupting the progress area. It is useful for
phase names, warnings, and completed file names.
Best Practices
Create the MultiProgress on the main thread, add bars before spawning workers, clone or
move each returned ProgressBar into the worker, and call join from the thread that owns
the terminal.
Styling
ProgressStyle combines a parsed template, progress characters, spinner frames, optional
line color, and custom keys.
Template Variables
| Variable | Format Spec | Description | Example Output |
|---|---|---|---|
{bar} | :40, :40.#/- | Progress bar | ########>------- |
{wide_bar} | none | Width-adaptive progress bar | fills remaining columns |
{pos} | none | Current position | 42 |
{count} | none | Compact current position | 1.2k |
{human_pos} | none | Alias for compact position | 1.2k |
{len} | none | Total length or ? | 100 |
{total_count} | none | Compact total length | 1.5M |
{human_len} | none | Alias for compact total length | 1.5M |
{remaining} | none | Remaining units | 58 |
{human_remaining} | none | Compact remaining units | 58.0k |
{ratio} | :.3 | Completion ratio | 0.420 |
{percent} | :.2 | Percentage without % | 42 or 42.50 |
{elapsed} | none | Human elapsed time | 3m 14s |
{elapsed_precise} | none | Precise elapsed time | 00:03:14 |
{elapsed_millis} | none | Millisecond elapsed time | 00:03:14.123 |
{eta} | none | Estimated remaining time | ~12s |
{eta_precise} | none | Precise ETA | 00:00:12 |
{eta_millis} | none | Millisecond ETA | 00:00:12.345 |
{per_sec} | :.2 | Average rate | 123.45 |
{rate} | none | Formatted item rate | 1.23k items/s |
{bytes} | none | Position as bytes | 1.00 MB |
{total_bytes} | none | Length as bytes | 50.00 MB |
{bytes_per_sec} | none | Byte throughput | 1.23 MB/s |
{spinner} | none | Current spinner frame | - |
{msg} | :20, :>20, :^20 | Message with optional padding/truncation | downloading |
{prefix} | :10, :>10, :^10 | Prefix with optional padding/truncation | task |
{postfix} | :10, :>10, :^10 | Postfix with optional padding/truncation | ok |
{status} | :10, :>10, :^10 | running, spinning, finished, or abandoned | running |
{wide_msg} | none, :20, :>20, :^20 | Terminal-width or explicit-width message field | terminal-width text |
Bar Format
{bar:N} sets the width. {bar:N.fill/empty} sets fill and empty characters. Color words
inside a template are accepted for compatibility with terminal-style templates; whole-line
color is configured with ColorSpec.
{wide_bar} uses the active draw target width. Width detection uses terminal APIs and falls
back to COLUMNS/LINES or safe defaults when a TTY is not available.
#![allow(unused)] fn main() { let style = loaders::ProgressStyle::with_template( "{prefix} [{bar:30.#/-}] {percent:.1}% {msg}", )?; Ok::<(), loaders::bar::template::TemplateError>(()) }
Reusable Progress Characters
ProgressChars stores fill, head, and empty characters as a reusable value.
#![allow(unused)] fn main() { use loaders::{ProgressChars, ProgressStyle}; let style = ProgressStyle::default_bar() .progress_chars_set(ProgressChars::blocks()); }
Use try_progress_chars when parsing user configuration and invalid inputs should return
an error.
Text Padding and Truncation
String variables support width specs for alignment and fixed width:
:20left-aligned width 20:>20right-aligned width 20:^20centered width 20
When the content is longer than the width it is truncated to fit.
Colors
#![allow(unused)] fn main() { use loaders::{Color, ColorSpec, ProgressStyle}; let style = ProgressStyle::default_bar() .color(ColorSpec::new().set_fg(Color::Cyan).set_bold(true)); }
Custom Keys
#![allow(unused)] fn main() { let style = loaders::ProgressStyle::with_template("{phase} {bar:20}")? .with_key("phase", |state| format!("phase {}", state.pos / 10 + 1)); Ok::<(), loaders::bar::template::TemplateError>(()) }
Themes
Every Theme returns a fully configured ProgressStyle.
| Theme | Description | 50% Preview | Code |
|---|---|---|---|
| Default | Portable ASCII | [###############>--------------] | Theme::Default.style() |
| Unicode | Heavy line style | [━━━━━━━━━━━━━━━╸░░░░░░░░░░░░░░] | Theme::Unicode.style() |
| Braille | Dense braille feel | [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣦⣦⣦] | Theme::Braille.style() |
| Block | Full block style | [███████████████▉▒▒▒▒▒▒▒▒▒▒▒▒▒▒] | Theme::Block.style() |
| Minimal | No box | =============== >.............. | Theme::Minimal.style() |
| Colorful | Bright colored output | [###############>--------------] | Theme::Colorful.style() |
| Gradient | Shaded blocks | [███████████████▓░░░░░░░░░░░░░░] | Theme::Gradient.style() |
| Retro | Classic equals arrow | [=============== >-------------] | Theme::Retro.style() |
| Dots | Dot progress | [•••••••••••••••∘ ] | Theme::Dots.style() |
| Rounded | Round markers | [●●●●●●●●●●●●●●●○──────────────] | Theme::Rounded.style() |
| Shaded | Small square shading | [▪▪▪▪▪▪▪▪▪▪▪▪▪▪▪▫ ] | Theme::Shaded.style() |
| Arrows | Directional fill | [>>>>>>>>>>>>>>>--------------] | Theme::Arrows.style() |
#![allow(unused)] fn main() { use loaders::{ProgressBar, Theme}; let pb = ProgressBar::new(100); pb.set_style(Theme::Block.style()); }
Advanced Usage
Custom Draw Targets
DrawTarget::to_writer accepts any Write + Send + 'static. This is useful for tests,
logs, or embedding progress output into a larger terminal abstraction.
#![allow(unused)] fn main() { let target = loaders::DrawTarget::to_writer(Vec::<u8>::new()); let pb = loaders::ProgressBar::with_draw_target(10, target); pb.inc(1); }
CI, Pipes, and NO_COLOR
Terminal detection checks common CI variables and stream capabilities. Non-TTY targets use
newline snapshots instead of cursor movement. The NO_COLOR environment variable disables
ANSI color output everywhere the crate emits color.
On Windows consoles, interactive targets enable virtual terminal mode so progress bars and spinners update on the same line when the terminal supports ANSI cursor controls.
Platform Coverage
loaders is dependency-free and uses std + platform APIs for terminal detection:
- Windows consoles use Win32 handles, console mode checks, and console width/height queries.
- Unix-family terminals (Linux, macOS, BSD, and similar) use
isattyandioctl.
The crate supports both 32-bit and 64-bit targets as long as Rust supports the target.
Hidden Bars in Tests
#![allow(unused)] fn main() { let pb = loaders::ProgressBar::hidden(); pb.inc(1); assert_eq!(pb.position(), 1); }
Custom Template Keys
Custom keys let applications render domain terms without forking the renderer.
#![allow(unused)] fn main() { let style = loaders::ProgressStyle::with_template("{phase} {bar}")? .with_key("phase", |state| format!("phase {}", state.pos / 10 + 1)); Ok::<(), loaders::bar::template::TemplateError>(()) }
Thread Safety Model
ProgressBar is a cloneable handle around shared state. Clone it into worker threads and
call inc, set_message, or finish from those threads. Each method locks briefly around
state changes and rendering.
Draw Rate Limiting
For high-throughput loops, set draw_delta to redraw every N items and draw_rate to cap
terminal writes per second.
#![allow(unused)] fn main() { let pb = loaders::ProgressBar::builder() .length(1_000_000) .draw_delta(1_000) .draw_rate(20) .build(); }
Capturing Output
Use a custom writer when assertions need rendered text. Use ProgressBar::hidden() when
the test only needs state.
Examples
The examples/ directory contains runnable demonstrations for common CLI situations.
basic_bar.rs
Shows a default bar and a Unicode themed bar.
default [##########>-----------------------------] 25/100 (25%) working
unicode [━━━━━━━━━━╸░░░░░░░░░░░░░░░░░░░░] 25/100 working
spinner.rs
Runs the dots, moon, and clock presets with automatic steady ticks and a final success symbol.
multi_progress.rs
Creates three bars, moves each into a worker thread, and calls multi.join() on the main
thread so the terminal block refreshes while work continues.
custom_style.rs
Demonstrates a custom template, progress_chars("=>-"), custom spinner frames, and a bold
cyan ColorSpec.
download_simulation.rs
Uses byte-aware variables:
download [########>-------------------------------] 12.00 MB/50.00 MB 1.34 MB/s ETA ~28s output.bin
nested_bars.rs
Shows an outer batch bar and an inner item bar using MultiProgress, with phase messages
printed above the block.
eta_and_rate.rs
Displays {per_sec:.2}, {eta}, and {elapsed} while simulated processing speed changes.
themed_bar.rs
Loops through every theme and renders a short visual gallery.
iterator_wrap.rs
Demonstrates .progress(), .progress_with_style(style), .progress_count(500), summing,
and chained iterators.
custom_template.rs
Registers custom_label with with_key and renders phase labels directly in the template.
advanced_customization.rs
Combines reusable ProgressChars, ANSI color, status-aware templates, compact remaining
counts, draw throttling, progress state reads, and finish_with_symbol.
custom_loader.rs
Shows custom loading animations using the meter, progress block, and pulse presets plus spinner status helpers.