# Spinners, a catalogue of terminal loaders for Rust

> The spinners crate gives a Rust program sixty-odd terminal spinner animations, chosen by name from an enum, and it can optionally drive a native terminal progress bar through the ConEmu OSC 9;4 escape sequence. It is a small crate with a large catalogue and one real trap in its signal handling.

**FGRibreau/spinners** — 🛎 60+ Elegant terminal spinners for Rust

- Repository: https://github.com/FGRibreau/spinners
- Website: https://fgribreau.com
- Stars: 600 · Forks: 29
- Language: Rust
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/fgribreau-spinners

## A spinner is a frame list and a timer

The spinners crate is described as sixty-plus elegant terminal spinners for Rust, and the whole library exists so that a command line program can print a moving indicator while it waits.

The usage is four lines of Rust, and they show the whole API surface.

```rust
use spinners::{Spinner, Spinners};
use std::thread::sleep;
use std::time::Duration;

fn main() {
    let mut sp = Spinner::new(Spinners::Dots9, "Waiting for 3 seconds".into());
    sleep(Duration::from_secs(3));
    sp.stop();
}
```

A `Spinner` is created with a variant from the `Spinners` enum and a message, and the spinner runs until you stop it. `Spinners::Dots9` is one of the catalogue entries, and the enum is generated so that every animation in the collection is a named variant rather than a string you have to remember. The message is a `String` built with `.into()` from a string literal.

The crate itself is thin. The dependency list in the packaging metadata is three crates: lazy_static for the global frame tables, maplit for the macro that writes the collections out, and strum with its derive feature, which is what generates the `Spinners` enum and the `Display` implementation for it. The strum derive is why the enum can be turned into a list of available names at runtime rather than being hand-maintained.

The `sp.stop()` call at the end is not optional decoration. It is what erases the spinner line and leaves the message, and because the spinner owns its terminal state, dropping it without stopping is what causes the leftover-output problem that the optional feature's documentation warns about later on.

The audience is Rust developers building command line tools, and the crate classifies itself under command-line-interface.

## The catalogue lives in a source file you can read

The number in the project description, sixty-plus, is the selling point, and it is worth understanding where those animations come from and how you find them.

The README links to a file in the source tree, `src/utils/spinner_names.rs`, as the list of available spinners, and points to the generated documentation on docs.rs for the full reference. So the enum variants in the code are the authoritative list, and the crate ships a source file that enumerates them.

The repository layout shows how the crate is assembled. There is `src/` with the implementation, an `examples/` directory with five runnable programs, `assets/` holding the sponsor logos, and a script called `update_spinners.sh` at the root. That last file is a clue about how the collection is maintained: the frames are curated and pulled in from elsewhere rather than drawn by hand one at a time, and a shell script does the updating. The result is a catalogue that grows in batches rather than one contribution at a time, which is also why the count moves in jumps.

The examples directory is the fastest way to see the collection in motion. There are `cycle.rs`, `simple.rs`, `stop_persist.rs`, `stop_symbol.rs` and `timer.rs`. The names tell you what each one demonstrates: cycling through spinners, the basic case, keeping the output on screen after stopping, choosing a stop symbol, and a timer variant.

The two to run first are the ones the README highlights.

```bash
cargo run --example cycle
```

```bash
cargo run --example simple
```

The `cycle` example is the one to run if you are deciding whether to depend on this crate, because it shows the whole catalogue rather than one animation.

## osc-progress turns the spinner into a real progress bar

The feature flag is the more interesting engineering in this crate, and it is the part worth understanding before you enable it.

Enabling `osc-progress` gives native terminal progress bar support through the ConEmu OSC 9;4 protocol. Terminals such as Ghostty, Windows Terminal, iTerm2, Kitty and WezTerm render these as graphical progress bars in the title or tab bar, and terminals that do not support the sequence ignore it silently.

The flag is off by default and is enabled in the dependency declaration rather than in code.

```toml
[dependencies]
spinners = { version = "4.1.0", features = ["osc-progress"] }
```

What it actually does is narrower than the name suggests. The documentation states that the progress bar is emitted as an indeterminate or pulsing indicator while the spinner is active, and that it is cleared when the spinner is stopped or dropped. Indeterminate means it does not report a fraction complete. It is a native-looking heartbeat rather than a real progress bar, and if you were hoping to show 40 of 100 items done, this is not the mechanism.

There is a second condition that matters more than it first appears. Sequences are only emitted when the output stream is a terminal, so piped output is unaffected. That is the right default, since escape sequences in a redirected log file would be corruption rather than a feature.

The trade is that enabling this flag ties your program to a terminal capability that many terminals do not have and that no library can detect reliably at runtime, because the protocol is write-only in practice. On a terminal that ignores it, nothing happens. On a terminal that honours it, your users see a tab bar indicator. On a terminal that mis-handles it, you have a bug report to file.

## The signal handling caveat is the real trap

The documentation is unusually direct about the failure mode of the progress feature, and it is worth quoting the shape of it.

If the process is killed abruptly, for example with `SIGINT` from Ctrl+C or with `SIGKILL`, the `Drop` implementation may not run and the progress bar will not be cleared. Terminals such as Ghostty mitigate this with an approximately fifteen-second auto-clear timeout, but for immediate cleanup, applications should install their own signal handler that stops the spinner, either by dropping it or by calling `.stop()`, before exiting.

This is a real design limitation rather than a documentation gap. The clearing sequence is written to the terminal as part of ordinary cleanup, and an abrupt signal bypasses ordinary cleanup. There is no way for a library to send the sequence after the process has been told to terminate, and the fifteen-second auto-clear in some terminals is a mitigation, not a fix.

The consequence for your program is concrete. If a user hits Ctrl+C during a long download or a long build, they may be left with a progress bar frozen in their tab bar until the terminal gives up on it. The fix is on your side of the API boundary, since you own the signal handling, and it is a few lines.

The same applies to the spinner line itself, not only the native bar. A spinner that is not stopped leaves its last frame and message on the terminal, and in a script that captures output that residue ends up in your logs.

This is also the one place where the crate's behaviour differs by environment in a way you cannot test for locally, because SIGKILL cannot be handled at all, so no handler can cover it.

## What the crate does not give you

The scope is narrow, and the boundaries are worth naming before you add the dependency.

There is no percentage-based progress. The optional integration is an indeterminate indicator, and the crate's own feature is the spinner. If your program knows how much work is left, this library does not model that, and you would be reaching for a different crate or writing the escape sequences yourself.

There is also no concurrency model. The usage example sleeps on the main thread for three seconds, and the API is a single `Spinner` value you own. There is no background thread that the crate starts for you, no message queue, and no way to update the text from another thread without doing the synchronisation yourself. A spinner that updates its message from a worker thread is a common requirement, and it is not what this API is shaped for.

The dependency cost is worth a mention too, though it is small. Three crates, all of them common ones, and the strum derive is the only one with any weight. The packaging metadata restricts what is published with `include = ["src/**/*", "README.md"]`, so the assets directory does not ship in the crate, which keeps the download small.

The version scheme is another thing to note. The crate is at 4.2.0 with the 2021 edition of Rust, the recorded release is v4.2.0 from 2026-03-04, and the last push to the repository was on 2026-05-02. The README shows the `osc-progress` example pinned to version 4.1.0, so the flag existed before the current release, and the minor version bump since then was elsewhere.

The licence is MIT, credited to François-Guillaume Ribreau and James Cordor, so there is no question about using it in a closed-source CLI.

## A dependency decision rather than a feature decision

It is easy to look at this crate and see sixty animations, which sounds like a nice-to-have. The more useful question is whether you want a dependency at all.

The counter-argument is that a spinner is about twenty lines of code: a frame list, a timer, a print, a carriage return, and an erase on exit. The `Spinners` enum with its sixty variants, the strum derive, and the lazy_static frame tables exist so that you do not have to write or copy that yourself, and so that the animations look like the ones people expect rather than a lone ASCII star.

That is a real benefit for a CLI whose users will look at the output. A spinner is one of the few places where a command line program is judged on its polish, and a distinctive animation is the kind of detail that gets noticed. The crate also gives you the same animation set on every platform for free, and it handles the terminal detection and the erase sequence for you.

The cost is a dependency in the dependency tree of a tool that people install globally, where every dependency is one more thing to keep current and one more place for a supply chain question to come up. For a CLI distributed as a binary, that is a real consideration, and it is worth noting that the crate's own dependency set is small enough that vendoring it is not impractical.

Where the crate is harder to argue against is the `osc-progress` flag. Getting a native tab bar indicator right, with the terminal detection and the cleanup on exit, is more work than most people want to do for a spinner, and the documentation tells you exactly which terminals support it. That is the feature to decide on.

For the rest, the decision is a taste decision, and the taste decision is cheap to reverse because the API surface is four calls. If the crate does not fit, writing your own takes an afternoon and the result is smaller than the dependency you avoided.

## Conclusion

Spinners suits a Rust developer writing a command line tool that needs a loading indicator and wants the animation to look like the sixty-odd curated ones rather than a hand-rolled ASCII loop. It does not suit a library author who needs a progress bar for a known quantity, since the optional integration is an indeterminate indicator rather than a measurable bar, and it does not suit a program that is killed abruptly, because the escape sequence that clears the bar may never be sent. Verify first that your terminal either supports OSC 9;4 or ignores it silently, that the spinner output is suppressed when stdout is not a terminal, and that you install a signal handler if a Ctrl+C during a long operation would leave a stray progress indicator behind.

## FAQ

### What is the spinners crate for Rust?

It provides sixty-plus terminal spinner animations for Rust command line programs, selected by name from the Spinners enum, so a program can show a moving indicator while it waits. The crate is published on crates.io as spinners.

### How do I add a spinner to a Rust program?

Create a Spinner with Spinner::new(Spinners::Dots9, "message".into()), keep it in a mutable variable, and call sp.stop() when the work is done. The usage example in the documentation sleeps for three seconds between the two calls.

### What does the osc-progress feature flag do?

It enables native terminal progress bar support through the ConEmu OSC 9;4 protocol, so terminals such as Ghostty, Windows Terminal, iTerm2, Kitty and WezTerm render a graphical bar in the title or tab bar, and unsupported terminals ignore the sequences silently. The bar is indeterminate and is only emitted when the output stream is a terminal.

### What happens to the progress bar if my program is killed with Ctrl+C?

The documentation warns that if the process is killed abruptly with SIGINT or SIGKILL the Drop implementation may not run and the progress bar will not be cleared. Terminals such as Ghostty mitigate this with a roughly fifteen-second auto-clear, but for immediate cleanup the application should install its own signal handler that stops the spinner before exiting.

### How do I see all the available spinners?

The README links to the list in src/utils/spinner_names.rs, and the generated API reference on docs.rs carries the full Spinners enum. Running cargo run --example cycle also plays them through in the terminal.

## Sources

- [FGRibreau/spinners on GitHub](https://github.com/FGRibreau/spinners)
- [License: MIT](https://github.com/FGRibreau/spinners/blob/master/LICENSE)
- [Project website](https://fgribreau.com)
- [README](https://github.com/FGRibreau/spinners/blob/master/README.md)
- [Releases](https://github.com/FGRibreau/spinners/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fgribreau-spinners
