A minimal, zero-dependency terminal spinner for Rust applications. Supports single and multi-spinner modes.

Inspired by the Node.js nanospinner npm package, nanospinner gives you a lightweight animated spinner using only the Rust standard library — no heavy crates, no transitive dependencies, builds in .2 seconds.
Part of the nano crate family — zero-dependency building blocks for Rust.
Comparison
nanospinner |
spinach |
spinoff |
indicatif |
|
|---|---|---|---|---|
| Dependencies | 0 | 0 | 3 | 6 |
| Clean Build Time | ~0.2s | ~0.2s | ~1.2s | ~1.4s |
| Customizable Frames | Default Braille set | Yes | Yes (80+ sets) | Yes |
| Multiple Spinners | Yes | No | No | Yes |
| Auto TTY Detection | Yes | No | No | Yes |
| Custom Writer | Yes (io::Write) | No | Stderr only | Yes (custom trait) |
| Thread-Safe Handles | Yes (Send) |
No | No | Yes (Send + Sync) |
| Progress Bars | No | No | No | Yes |
| Async Support | No | No | No | Optional (tokio feature) |
Build times measured from a clean cargo build --release on macOS aarch64 (Apple Silicon). Your numbers may vary by platform.
Quick Start
Add nanospinner to your project:
use Spinner;
use thread;
use Duration;
Usage
For the full API, see the docs.rs documentation.
Single Spinner
Spinner::new(msg).start() spawns a background thread that animates the spinner. It returns a SpinnerHandle you use to update or finalize the spinner. Calling success(), fail(), warn(), or info() stops the thread and prints the final line — no separate stop() needed. If you drop the handle without finalizing, the thread is joined and the line is cleared automatically.
use Spinner;
use thread;
use Duration;
// Basic: start, wait, finalize
let handle = new.start;
sleep;
handle.success; // ✔ Downloading...
// Update mid-spin, finalize with a replacement message
let handle = new.start;
sleep;
handle.update;
sleep;
handle.success_with; // ✔ All steps complete
Multi-Spinner
MultiSpinner manages multiple spinner lines with a single background render thread. Finalizing a line (success, fail, clear) only updates that line's status — the render thread keeps running. Call stop() on the group handle (or let it drop) to shut down the render thread.
use MultiSpinner;
use thread;
use Duration;
let handle = new.start;
let line1 = handle.add;
let line2 = handle.add;
sleep;
line1.success;
line2.fail_with;
handle.stop;
// Thread-based: move line handles to worker threads
let handle = new.start;
let workers: =
.map
.collect;
for w in workers
handle.stop;
Custom Writers and TTY Detection
Both Spinner and MultiSpinner auto-detect whether stdout is a terminal. When it isn't (piped, redirected), animation and ANSI codes are skipped — only plain text is printed:
$ my_tool | cat
✔ Done!
For custom output targets, both offer with_writer and with_writer_tty constructors:
// Custom writer (defaults to non-TTY — no ANSI codes)
let handle = with_writer.start;
let handle = with_writer.start;
// Custom writer with explicit TTY control
let handle = with_writer_tty.start;
let handle = with_writer_tty.start;
Contributing
Contributions are welcome. To get started:
- Fork the repository
- Create a feature branch (
git checkout -b my-feature) - Make your changes
- Run the tests:
cargo test - Submit a pull request
Please keep changes minimal and focused. This crate's goal is to stay small and as dependency-free as possible.
License
This project is licensed under the MIT License.