CLI tool
vyfor/rattles avatar
vyfor/rattles

Rattles: a dependency-free spinner library for Rust, with a no_std mode that hands you the clock

🪇 Minimal terminal spinners/throbbers for Rust

1,007 stars22 forksRustApache-2.0

At a glance

What is it?
Rattles is a small Rust crate of terminal spinners that assumes nothing about how you render them, and it can drop the std feature so the animation is driven by your own clock, index or tick. Version 0.3.1 in the repository is dual licensed MIT OR Apache-2.0, and the crate has no GitHub releases.
Who is it for?
Adopt Rattles if you are writing a Rust CLI or a ratatui interface and want spinner frames without pulling in a dependency tree, since the crate ships with no dependencies and works without std. Do not adopt it expecting a terminal control layer, because it makes no assumptions about how the output is used and you still own the write and flush calls.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 134 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A spinner crate with no dependencies and no opinions

Rattles is described as a minimal, dependency-free terminal spinner or throbber library for Rust, and the second half of that description is the important part: it makes no assumptions about how the output will be used.

Most spinner libraries in other ecosystems take over your terminal, install a signal handler, spawn a thread and draw frames for you. Rattles does not. It gives you a value that knows its current frame, and you decide what to do with it. The manifest backs up the dependency-free claim, since the Cargo.toml lists no dependencies section at all, only dev-dependencies of crossterm 0.29 and ratatui 0.30 for the examples.

The crate itself is small. The Cargo.toml declares crate-type as lib, edition 2024, and version 0.3.1, and the categories are command-line-interface, no-std and no-std::no-alloc, with keywords covering terminal, tui, spinner, throbber and ratatui.

There is a small mismatch worth noting between the repository metadata and the project's own statement. The repository records the licence as Apache-2.0, while the README and the Cargo.toml both say the crate is dual licensed under MIT OR Apache-2.0 at your discretion, with the details in LICENSE-MIT and LICENSE-APACHE. The dual licence is the more specific claim and the one to rely on.

There are also no GitHub releases for the repository, so the version you get is whatever is on the crate registry, which the manifest currently puts at 0.3.1. The last push was on 2026-06-11.

The minimal example, and the two options you will change first

The quick start is a single cargo add line.

sh
cargo add rattles

The minimal example is a loop that prints the current frame with a carriage return, flushes stdout and sleeps.

rust
use std::{io::Write, time::Duration};
use rattles::presets::prelude as presets;

fn main() {
    let rattle = presets::waverows();

    loop {
        print!("\r{}", rattle.current_frame());
        std::io::stdout().flush().unwrap();
        std::thread::sleep(Duration::from_millis(20));
    }
}

Three things in that snippet carry the design. The frame is fetched with current_frame() rather than pushed to the terminal, so the crate never touches your stdout. You flush explicitly, because a spinner that sits in a buffer looks frozen. And you sleep yourself, so the frame rate is under your control rather than being a background thread's business.

Two builder options are shown as comments in the README. The interval can be changed with set_interval, taking a Duration, and the direction can be flipped with reverse(). Neither is passed at construction, so the same preset constructor gives you a configurable object.

The presets are grouped by category, and the README names four of them: arrows, ascii, braille, emoji. There is a prelude at rattles::presets::prelude, and the example imports it with use rattles::presets::prelude as presets. The categories exist because of terminal font coverage, which is the same reason the emoji category is worth thinking about twice before you rely on it.

Writing your own keyframes with the rattle! macro

Custom animations are declared with a macro rather than built through a builder chain.

rust
rattle!(
    Custom, // struct name
    custom, // method name
    1,      // row count (width of the spinner)
    100,    // interval in milliseconds
    ["⣾", "⣷", "⣯", "⣟", "⣻", "⣽", "⣾"] // keyframes
)

The macro takes the struct name, the method name that produces it, the row count which the README describes as the width of the spinner, the interval in milliseconds, and the array of keyframe strings.

The row count parameter is the part that tells you what this library is for. A spinner is not necessarily one line of text, and the braille characters in the example are single columns of a taller block, so the row count tells the library how many rows the animation occupies. That is a detail a naive spinner library would not care about, and it suggests the crate was written with multi-row animations in mind from the start.

The interval lives in the macro rather than only at runtime, which means a custom spinner carries its own timing as part of its definition, and set_interval can still override it afterwards.

no_std and the three ways to drive the animation

The std feature is enabled by default, and you opt out of it explicitly.

sh
cargo add rattles --no-default-features

The reason is spelled out in the README. Without std, the global clock is unavailable, so the library cannot decide on its own when the next frame is due. That is a design decision with real consequences, and the crate handles it by giving you three ways to drive the animation.

The first is time based with an external clock, calling rattle.frame_at(elapsed). You pass the elapsed time yourself, which means you decide what the time base is and where it comes from, whether that is a monotonic instant in your event loop or a timestamp from an outer system.

The second is index based, calling rattle.frame(n). You choose the frame number, which is useful when the animation is driven by something external such as a progress counter or a network event.

The third is tick based.

rust
let mut rattle = presets::dots().into_ticked();
rattle.tick();
let frame = rattle.current_frame();

Here the rattle is converted with into_ticked and then advanced one step at a time with tick(). The README adds a warning that matters in practice: TickedRattler is stateful and must be stored outside the render loop. If you construct it inside the loop, or if you rebuild it each iteration, the state is lost and the animation does not advance in a way you intended.

Two examples ship with the crate, a minimal ratatui example and a no_std oriented example, and there is a third example file alongside them, examples/showcase.rs.

Where Rattles is the wrong choice

The scope is narrow on purpose, and that narrowness is the limitation.

There is no terminal handling. No cursor hiding, no carriage return management, no cleanup on Ctrl+C, no signal handling, no cursor restore after a panic. The library hands you a frame and steps out of the way, which is exactly what you want inside a TUI that owns the terminal, and exactly what you do not want if you expected a drop-in progress indicator for a long running command.

The documented mechanism is frames and intervals, so a throbber in the sense of a growing and shrinking bar is something you would assemble from keyframes yourself.

The dependency-free claim has a cost too. There is no built-in colour support, no Unicode width calculation, and no handling of terminals that do not support the characters you chose. If your spinner uses braille glyphs, a terminal without a font covering them shows replacement characters, and the crate will not warn you. The emoji category carries the same risk more sharply.

And the project is pre-1.0 with no tagged releases. The Cargo.toml version is 0.3.1, so the API can still change, and since the repository has no GitHub releases there is no changelog to track the changes against. Pin the version if you ship something.

Editorial conclusion

Adopt Rattles if you are writing a Rust CLI or a ratatui interface and want spinner frames without pulling in a dependency tree, since the crate ships with no dependencies and works without std. Do not adopt it expecting a terminal control layer, because it makes no assumptions about how the output is used and you still own the write and flush calls. Verify first that your terminal renders the braille and emoji presets, since those keyframes depend on the font, and check the version you pull, because the repository carries no GitHub releases and the manifest shows 0.3.1.

Frequently asked questions

How do I add the Rattles spinner crate to a Rust project?

Run cargo add rattles. The crate enables the std feature by default, and you opt out of it with cargo add rattles --no-default-features when you want to run without the standard library.

How do I create a custom spinner animation in Rattles?

Use the rattle! macro, giving it a struct name, a method name, the row count which is the width of the spinner, an interval in milliseconds and the array of keyframe strings. The README shows a braille example with seven keyframes.

How do I drive a Rattles animation without the std feature?

Without std the global clock is unavailable, so you drive it yourself in one of three ways: time based with rattle.frame_at(elapsed), index based with rattle.frame(n), or tick based by converting with into_ticked() and calling rattle.tick(). The ticked variant is stateful and must be stored outside the render loop.

What licence is Rattles released under?

The README and the Cargo.toml both state that it is dual licensed under MIT OR Apache-2.0 at your discretion, with the details in the LICENSE-MIT and LICENSE-APACHE files. The repository's own licence field lists only Apache-2.0, so the dual licence statement is the more specific one.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. Project website
  4. README
  5. vyfor/rattles on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/vyfor-rattles.svg)](https://hysenlabs.com/projects/vyfor-rattles)

Was this useful?