spinners

60+ elegant animated terminal spinners for Rust CLI tools, with built-in stop messages, symbols, and OSC 9;4 progress support.

Library
Cargo
v4.2.0
601stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
40/100Fair
Development Activity12
Maintenance20
Community40
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
65/100Good
Architecture78
Code Quality45
Innovation82
Learning Curve55

spinners is a Rust crate that renders animated terminal loading indicators — over 60 spinner styles ported from the popular cli-spinners set — while your program does other work. Each spinner runs on its own background thread, writing frames to stdout or stderr on an interval, and can be stopped with a plain stop, a custom symbol, a persisted message, or a combination of both.

Beyond animation, the crate optionally emits ConEmu-style OSC 9;4 escape sequences so terminals like Ghostty, Windows Terminal, iTerm2, Kitty, and WezTerm can render a native indeterminate progress indicator in the tab or title bar alongside the spinner, gated behind the osc-progress feature flag and only active when output is a real terminal.

What You Get

  • 60+ built-in spinner animations ported from the cli-spinners frame data set, selectable via the Spinners enum
  • A Spinner struct that runs on a dedicated background thread and cleans itself up via Drop
  • Four stop variants — stop, stop_with_symbol, stop_with_newline, and stop_and_persist — for different completion states
  • Optional osc-progress feature flag that emits OSC 9;4 escape sequences for native terminal progress bars in Ghostty, Windows Terminal, iTerm2, Kitty, and WezTerm
  • Configurable output stream (Stream::Stdout or Stream::Stderr) and an optional elapsed-timer display via with_timer

Common Use Cases

  • Showing progress while a CLI tool waits on a network request, build step, or file operation
  • Reporting task completion with a checkmark or custom symbol instead of leaving a bare spinner on screen
  • Timing long-running CLI operations and displaying elapsed seconds next to the spinner
  • Giving terminal emulators with OSC 9;4 support a native taskbar/tab progress indicator alongside CLI output

Under The Hood

Architecture The crate is a small, modular single-purpose library: the public Spinner struct in src/lib.rs holds only a Sender, a JoinHandle, and a Stream, while the actual frame data lives in src/utils/spinners_data.rs, the enum of spinner names in src/utils/spinner_names.rs, and the terminal-writing logic in src/utils/stream.rs. Spinner::new_inner spawns a background thread that loops over spinner_data.frames, polling a channel each frame to detect a stop signal, and delegates the actual write to Stream::write; the public struct’s Drop implementation joins that thread automatically if stop() was never called explicitly, so callers can’t leak a spinner thread. Because the stop protocol is a hand-rolled mpsc channel carrying (Instant, Option<String>), any change to how spinners are stopped requires updating both new_inner’s receive loop and stop_inner’s send call in lockstep.

Tech Stack The crate targets Rust edition 2021 and has a deliberately small dependency footprint: strum (with the derive feature) generates Display/FromStr for the Spinners enum so spinner names can round-trip to and from plain strings, lazy_static builds the static frame-data map once, and maplit is used to construct that map’s literal entries. There is no async runtime — concurrency is plain std::thread plus std::sync::mpsc — and no I/O beyond std::io::{stdout, stderr}. The optional osc-progress feature adds no new dependencies; it just gates extra write! calls behind std::io::IsTerminal checks. Distribution is via crates.io, with runnable examples invoked through cargo run --example.

Code Quality No test files exist anywhere in the repository — coverage relies entirely on doc-comment examples (which are compiled as Rust doctests) attached to nearly every public method. Error handling favors .unwrap()/.expect() at thread-boundary points (joining the spinner thread, sending on the stop channel) rather than propagating Result, which is a reasonable tradeoff for a spinner but means a failed send or a poisoned thread will panic rather than degrade gracefully. Naming is idiomatic Rust (snake_case methods, PascalCase types), the public surface is enum-driven rather than stringly-typed, and there is no CI workflow configured in .github/ — only a FUNDING.yml — so builds and doctests aren’t automatically verified on push.

API Design Getting started takes two lines: pick a Spinners variant and call Spinner::new(spinner, message). The four stop methods (stop, stop_with_symbol, stop_with_newline, stop_and_persist) map cleanly onto the real-world ways a CLI task ends, and with_timer/with_stream/with_timer_and_stream compose the same underlying constructor rather than forking the API. Deriving Display/FromStr on Spinners via strum means a spinner can be chosen from a CLI argument (Spinners::from_str(&arg)) with no extra glue code, and every public method carries a runnable doc example, keeping the crate’s documentation directly executable and up to date.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search