tui-spinner
Customizable spinner widgets for Ratatui TUI applications.
Widgets
| Widget | Shape | Motion | Key options |
|---|---|---|---|
LinearSpinner |
Straight line | Scrolling window or bouncing dot | direction, flow, style |
SquareSpinner |
Square braille ring | Rotating arc | size, spin, centre |
CircleSpinner |
Circular braille ring | Rotating arc | radius, spin, arc_len |
RectSpinner |
Rectangle braille ring | Rotating arc | shape, spin, centre |
BarSpinner |
Solid horizontal bar | Bouncing glow (ping-pong) | width, height, arc_width, spin |
FluxSpinner |
Single-character glyph | Cycling frame sequence | frames, width, phase_step, spin |
Previews
SquareSpinner — Filled & Empty · CW ↻ and CCW ↺

CircleSpinner — Clockwise ↻ and Counter-Clockwise ↺

LinearSpinner — Orientation & Direction

BarSpinner — Zed / Claude-style bouncing bar

FluxSpinner — All frame presets

Installation
[]
= "0.1"
Quick Start
use Color;
use ;
// Vertical bouncing dot (Zed / Copilot style)
let v = new
.direction
.linear_style
.active_color;
// Square arc, clockwise, filled centre
let sq = new
.size
.spin
.centre
.arc_color;
// Circular arc, counter-clockwise
let circle = new
.radius
.spin
.arc_color;
// Zed / Claude-style bouncing bar — fills available width automatically
let bar = new
.arc_color
.dim_color;
// Minimal 1×1 status-bar spinner
let flux = new
.color;
// Wave of rotating glyphs
let wave = new
.frames
.width
.phase_step
.color;
All widgets are stateless — just pass a monotonically-increasing tick: u64 counter:
Running the Examples
Widget Reference
LinearSpinner
Horizontal scrolling window or vertical bouncing dot.
Horizontal (Forwards): ●●· → ·●● → ··● → ●·· → …
Horizontal (Backwards): ··● → ·●● → ●●· → ●·· → …
Vertical: ● · ·
· → ● → · → bounces back
· · ●
| Builder | Default | Description |
|---|---|---|
direction(Direction) |
Horizontal |
Animation axis |
flow(Flow) |
Forwards |
Animation direction |
linear_style(LinearStyle) |
Classic |
Symbol set for active/inactive slots |
total_slots(n) |
3 |
Total number of slots |
lit_slots(n) |
2 |
Simultaneously lit slots (horizontal only) |
ticks_per_step(n) |
3 |
Ticks per step (higher = slower) |
active_color(c) |
White |
Lit symbol colour |
inactive_color(c) |
DarkGray |
Dim symbol colour |
block(b) |
— | Optional Block wrapper |
style(s) |
— | Base widget style |
Direction
| Variant | Description |
|---|---|
Horizontal |
Scrolling window across a row (default) |
Vertical |
Single dot bouncing up and down a column |
Flow
| Variant | Description |
|---|---|
Forwards |
Normal direction — left-to-right / upward-first (default) |
Backwards |
Reversed — right-to-left / downward-first |
LinearStyle
| Variant | Active | Inactive |
|---|---|---|
Classic |
● |
· |
Square |
■ |
□ |
Diamond |
◆ |
◇ |
Bar |
┃ |
│ |
Braille |
⣿ |
⠿ |
Arrow |
▶/▼ |
▷/▽ |
SquareSpinner
Braille-dot arc rotating around a square ring, with optional filled centre.
| Builder | Default | Description |
|---|---|---|
size(n) |
2 |
Arc thickness / square size (2–8) |
spin(Spin) |
Clockwise |
Rotation direction |
centre(Centre) |
Filled |
Interior fill mode |
arc_color(c) |
White |
Rotating arc colour |
dim_color(c) |
DarkGray |
Centre fill / dim ring colour |
ticks_per_step(n) |
1 |
Ticks per arc step |
alignment(a) |
Left |
Horizontal alignment |
block(b) |
— | Optional Block wrapper |
Note:
SquareSpinneris a convenience wrapper aroundRectSpinner. For new code preferRectSpinner::new(tick).shape(RectShape::Square(n)).
CircleSpinner
Braille-dot arc rotating around a circular ring computed with the midpoint circle algorithm.
| Builder | Default | Description |
|---|---|---|
radius(n) |
4 |
Circle radius in braille dots |
arc_len(n) |
0 (auto ¼) |
Bright arc length in perimeter dots |
spin(Spin) |
Clockwise |
Rotation direction |
ticks_per_step(n) |
1 |
Ticks per arc step |
arc_color(c) |
White |
Bright arc colour |
dim_color(c) |
DarkGray |
Dim ring colour |
alignment(a) |
Left |
Horizontal alignment |
block(b) |
— | Optional Block wrapper |
Use .char_size() → (cols, rows) to query the exact rendered size.
RectSpinner
Braille-dot arc rotating around a configurable rectangle.
| Builder | Default | Description |
|---|---|---|
shape(RectShape) |
Square(2) |
Shape and size |
spin(Spin) |
Clockwise |
Rotation direction |
centre(Centre) |
Filled |
Interior fill mode |
outer_color(c) |
Cyan |
Rotating arc colour |
inner_color(c) |
DarkGray |
Centre fill colour |
ticks_per_step(n) |
1 |
Ticks per arc step |
alignment(a) |
Left |
Horizontal alignment |
block(b) |
— | Optional Block wrapper |
RectShape
| Variant | Description |
|---|---|
Square(n) |
Square output; n = arc thickness / size (2–8) |
Spin (shared)
| Variant | Description |
|---|---|
Clockwise |
Arc travels clockwise (default) |
CounterClockwise |
Arc travels counter-clockwise |
Centre (shared)
| Variant | Description |
|---|---|
Filled |
Solid interior block (default) |
Empty |
No interior fill |
BarSpinner
Zed / Claude-style solid braille bar with a soft glowing arc that bounces
left and right (ping-pong). The arc edges taper through a density ramp
(⠉ ⠛ ⠿ ⣿) for a comet-glow look.
Set width(0) (the default) to fill the available area automatically.
| Builder | Default | Description |
|---|---|---|
width(n) |
0 (auto-fill) |
Fixed column count; 0 = fill area |
height(n) |
1 |
Bar height in rows (1 = thin Zed-style) |
arc_width(n) |
0 (auto ⅓) |
Bright arc width in character columns |
spin(Spin) |
Clockwise |
Starting direction before first bounce |
ticks_per_step(n) |
1 |
Ticks per step (higher = slower) |
arc_color(c) |
Cyan |
Bright arc colour |
dim_color(c) |
DarkGray |
Background track colour |
alignment(a) |
Left |
Horizontal alignment |
block(b) |
— | Optional Block wrapper |
// Full-width single-row bar (most common usage)
let bar = new
.arc_color
.dim_color;
// Claude-style warm 2-row banner
let claude = new
.height
.arc_color
.dim_color;
FluxSpinner
A compact single-character spinner that cycles through a frame sequence.
At 1×1 it is a minimal status-bar indicator; scaled up with
width / height it
produces a travelling diagonal wave controlled by
phase_step.
| Builder | Default | Description |
|---|---|---|
frames(f) |
FluxFrames::BRAILLE |
Frame sequence (any &'static [char]) |
width(n) |
1 |
Width in character columns |
height(n) |
1 |
Height in character rows |
spin(Spin) |
Clockwise |
Frame sequence direction |
ticks_per_step(n) |
1 |
Ticks per frame (higher = slower) |
phase_step(n) |
1 |
Frame offset between adjacent cells |
color(c) |
Cyan |
Glyph colour |
alignment(a) |
Left |
Horizontal alignment |
block(b) |
— | Optional Block wrapper |
FluxFrames presets
| Preset | Glyphs | Frames | Description |
|---|---|---|---|
BRAILLE |
⣾ ⣷ ⣯ ⣟ ⡿ ⢿ ⣽ ⣻ |
8 | Full cell, one dot missing (default) |
ORBIT |
⠁ ⠈ ⠐ ⠠ ⢀ ⡀ ⠄ ⠂ |
8 | Single dot orbiting |
CLASSIC |
⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ |
10 | Classic braille spinner |
LINE |
│ ╱ ─ ╲ |
4 | Rotating line |
BLOCK |
▖ ▘ ▝ ▗ |
4 | Quarter-block rotation |
ARC |
◜ ◝ ◞ ◟ |
4 | Quarter-arc rotation |
CLOCK |
◷ ◶ ◵ ◴ |
4 | Quarter-circle pie slice |
MOON |
◓ ◑ ◒ ◐ |
4 | Half-circle moon phase |
TRIANGLES |
▲ ▶ ▼ ◀ |
4 | Filled triangle rotation |
PULSE |
⣀ ⣤ ⣶ ⣾ ⣿ ⣾ ⣶ ⣤ |
8 | Braille fill pulse |
BOUNCE |
⠉ ⠒ ⣀ ⠒ |
4 | Braille dot bouncing |
HALF |
▀ ▐ ▄ ▌ |
4 | Half-block rotation |
SQUARE |
◰ ◳ ◲ ◱ |
4 | Filled square quadrant |
DICE |
⚀ ⚁ ⚂ ⚃ ⚄ ⚅ |
6 | Dice faces |
BAR |
▁ ▂ ▃ ▄ ▅ ▆ ▇ █ |
8 | Growing bar |
CORNERS |
┌ ┐ ┘ └ |
4 | Box corners |
CIRCLE_FILL |
○ ◔ ◑ ◕ ● |
5 | Circle filling |
PISTON |
▁ ▃ ▅ ▇ █ ▇ ▅ ▃ |
8 | Bouncing bar |
STAR |
✶ ✷ ✸ ✹ |
4 | Star density ramp |
PAIR |
⠉ ⠘ ⠰ ⢠ ⣀ ⡄ ⠆ ⠃ |
8 | Two dots rotating together |
DIAMOND |
◇ ◈ ◆ ◈ |
4 | Diamond pulse |
Pass any &'static [char] for a fully custom animation:
let custom = new
.frames
.color;
phase_step wave effect
width = 6, phase_step = 1, Clockwise
⣾⣷⣯⣟⡿⢿ (tick 0)
⣷⣯⣟⡿⢿⣽ (tick 1)
⣯⣟⡿⢿⣽⣻ (tick 2) …
With Spin::CounterClockwise the wave travels in the opposite direction.
Generating Demo GIFs
# Requires VHS: https://github.com/charmbracelet/vhs
# Or via justfile:
GIF files are tracked with Git LFS (see .gitattributes).
License
MIT — see LICENSE.