rattles-terminal-spinners

Minimal terminal spinner library for Rust with preset collection and no-std support

INSTALLATION
npx skills add https://github.com/aradotso/trending-skills --skill rattles-terminal-spinners
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$27

cargo add rattles

# no_std variant

cargo add rattles --no-default-features

Core Concepts

  • Rattler: a spinner definition (frames + interval). Stateless and cheap to construct.
  • TickedRattler: stateful wrapper for tick-based driving (required in no_std).
  • Presets: built-in spinners organized by category.
  • **rattle! macro**: define custom spinners at compile time.

Basic Usage (std)

use std::{io::Write, time::Duration};

use rattles::presets::prelude as presets;

fn main() {

    let rattle = presets::dots();

    loop {

        print!("\r{}", rattle.current_frame());

        std::io::stdout().flush().unwrap();

        std::thread::sleep(Duration::from_millis(80));

    }

}

current_frame() uses the system clock internally — no state needed.

Preset Categories

use rattles::presets::{arrows, ascii, braille, emoji};

use rattles::presets::prelude as presets; // re-exports all presets

// Arrows

let s = arrows::arrow();

let s = arrows::arrow2();

// ASCII

let s = ascii::line();

let s = ascii::pipe();

// Braille

let s = braille::dots();

let s = braille::dots2();

// Emoji

let s = emoji::earth();

let s = emoji::clock();

// Prelude examples

let s = presets::waverows();

let s = presets::dots();

Rattler API

use rattles::presets::prelude as presets;

use std::time::Duration;

let rattle = presets::dots();

// Get frame based on system clock (std only)

let frame: &str = rattle.current_frame();

// Get frame at specific elapsed duration (std + no_std)

let frame = rattle.frame_at(Duration::from_millis(500));

// Get frame by index

let frame = rattle.frame(3);

// Change animation interval

let rattle = presets::dots().set_interval(Duration::from_millis(50));

// Reverse direction

let rattle = presets::waverows().reverse();

// Convert to tick-based (stateful)

let mut ticked = presets::dots().into_ticked();

TickedRattler (Stateful / no_std-friendly)

use rattles::presets::prelude as presets;

let mut rattle = presets::dots().into_ticked();

loop {

    rattle.tick();

    let frame = rattle.current_frame();

    // render frame...

}

TickedRattler must be stored (it holds state). Suitable for no_std contexts where the global clock is unavailable.

Index-Based Animation (no_std)

use rattles::presets::prelude as presets;

let rattle = presets::dots();

let mut i = 0usize;

loop {

    let frame = rattle.frame(i);

    i = i.wrapping_add(1);

    // render frame...

}

Time-Based Animation with External Clock (no_std)

use rattles::presets::prelude as presets;

use core::time::Duration;

let rattle = presets::dots();

// elapsed comes from your platform's timer

let elapsed: Duration = get_elapsed(); // your implementation

let frame = rattle.frame_at(elapsed);

Custom Spinners with rattle! Macro

use rattles::rattle;

rattle!(

    MySpinner,   // generated struct name

    my_spinner,  // generated constructor function name

    1,           // row count (width of spinner)

    120,         // interval in milliseconds

    ["⣾", "⣷", "⣯", "⣟", "⣻", "⣽"]  // keyframes

);

// Use it like any preset

let s = my_spinner();

println!("{}", s.current_frame());

Multi-row custom spinner:

rattle!(

    Wide,

    wide_spinner,

    3,   // 3 characters wide

    80,

    ["[   ]", "[=  ]", "[== ]", "[===]", "[ ==]", "[  =]"]

);

Ratatui Integration

// examples/ratatui.rs pattern

use rattles::presets::prelude as presets;

use ratatui::{

    backend::CrosstermBackend,

    widgets::Paragraph,

    Terminal,

};

fn ui(frame: &mut ratatui::Frame, rattle: &rattles::Rattler) {

    let spinner_text = rattle.current_frame();

    let paragraph = Paragraph::new(format!("{} Loading...", spinner_text));

    frame.render_widget(paragraph, frame.size());

}

fn main() -> std::io::Result<()> {

    let rattle = presets::dots();

    // standard ratatui event loop

    loop {

        terminal.draw(|f| ui(f, &#x26;rattle))?;

        std::thread::sleep(std::time::Duration::from_millis(16));

        // break on user input...

    }

    Ok(())

}

Since Rattler is stateless, pass it by reference anywhere — no Arc<Mutex<>> needed.

no_std Setup

[dependencies]

rattles = { version = "0.1", default-features = false }
#![no_std]

use rattles::presets::prelude as presets;

// Option 1: tick-based

let mut rattle = presets::dots().into_ticked();

rattle.tick();

let frame = rattle.current_frame();

// Option 2: index-based

let rattle = presets::dots();

let frame = rattle.frame(42);

// Option 3: duration-based (external clock)

let rattle = presets::dots();

let frame = rattle.frame_at(core::time::Duration::from_millis(840));

Common Patterns

Spinner with message

use rattles::presets::prelude as presets;

use std::{io::Write, time::Duration};

fn main() {

    let rattle = presets::dots();

    let message = "Fetching data...";

    loop {

        print!("\r{} {}", rattle.current_frame(), message);

        std::io::stdout().flush().unwrap();

        std::thread::sleep(Duration::from_millis(80));

    }

}

Async-compatible (tokio)

use rattles::presets::prelude as presets;

use tokio::time::{sleep, Duration};

#[tokio::main]

async fn main() {

    let rattle = presets::dots();

    let spinner = tokio::spawn(async move {

        loop {

            print!("\r{}", rattle.current_frame());

            std::io::stdout().flush().unwrap();

            sleep(Duration::from_millis(80)).await;

        }

    });

    // do your async work

    do_work().await;

    spinner.abort();

    println!("\rDone!     ");

}

Collecting all frames

let rattle = presets::dots();

let frames: Vec<&#x26;str> = (0..rattle.frame_count())

    .map(|i| rattle.frame(i))

    .collect();

Troubleshooting

Spinner not animating (stuck on first frame)

  • Ensure you're flushing stdout: std::io::stdout().flush().unwrap()
  • Use \r to overwrite the line, not \n
  • The sleep interval should match or be shorter than the spinner's interval

**current_frame() not available in no_std**

  • Use frame_at(duration), frame(index), or into_ticked() instead
  • Disable default features: rattles = { version = "...", default-features = false }

Custom spinner not compiling

  • Keyframes must be string literals in the rattle! macro array
  • Row count must match the visual width of each keyframe string

Spinner looks garbled in terminal

  • Some braille/emoji frames require a terminal with Unicode support
  • Test with ASCII presets (ascii::line()) to verify basic functionality first
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card