tui-skeleton
Animated skeleton loading widgets for Ratatui.
Placeholder widgets that pulse, sweep, or shimmer while data loads. All widgets are stateless — pass elapsed_ms from your event loop and the animation state is computed purely from the timestamp.

Widgets
| Widget | Description |
|---|---|
SkeletonBlock |
Filled rectangle — the atomic unit |
SkeletonTable |
Rows with column separators, ragged cell widths, and zebra striping |
SkeletonList |
Short spaced items with ragged edges (menu/sidebar) |
SkeletonText |
Paragraph simulation with varying line widths |
SkeletonBarChart |
Vertical bars of varying height |
SkeletonHBarChart |
Horizontal bars of varying length |
SkeletonBrailleBar |
Braille progress bars with rounded caps and peak marker |
SkeletonKvTable |
Key-value pairs (properties/detail panel) |
SkeletonStreamingText |
Typewriter-style chat text filling over time |
SkeletonLineChart |
Braille line chart with overlapping wave traces |
Installation
Usage
use Color;
use ;
// Solid fill with sweep animation
let solid = new
.mode
.base
.highlight;
// Braille fill (⣿) with breathe animation
let braille = new
.braille
.base
.highlight;
// TV noise — random braille glyphs changing every frame
let noise = new
.mode;
All widgets share the same builder pattern:
use Constraint;
use ;
// Table with 3 columns
let table = new
.columns
.rows
.zebra;
// List (short spaced items)
let list = new
.items;
// Paragraph placeholder
let text = new
.line_widths;
Render conditionally based on your application state:
if data.is_loading else
Animation Modes
| Mode | Cycle | Pattern |
|---|---|---|
| Breathe (default) | 5s | Uniform pulse — subtle, passive loading indicator |
| Sweep | 2.8s | Traveling highlight left-to-right with cosine falloff |
| Plasma | 4s | Dual sine-wave interference for organic shifting patterns |
| Noise | — | Constant color; random braille glyphs change every frame (TV noise) |
Breathe

Sweep

Plasma

Noise

Colors
All widgets accept base and highlight colors for the interpolation endpoints:
base— dim resting state (default:DarkGray)highlight— peak brightness (default:Gray)
Defaults work on both dark and light terminals. Override to match your theme:
use Color;
use SkeletonBlock;
// Dark theme
let dark = new
.base
.highlight;
// Light theme
let light = new
.base
.highlight;
Adaptive Tick Rate
Skeleton animations look best at ~20 FPS. The crate exports recommended intervals:
use ;
// TICK_ANIMATED = 50ms (20 FPS)
// TICK_IDLE = 200ms (5 FPS)
The recommended pattern:
- Track whether any skeleton is currently visible
- Use
TICK_ANIMATEDwhen skeletons are on screen - Revert to
TICK_IDLEwhen all data has loaded
Builder Methods
All widgets support:
| Method | Default | Description |
|---|---|---|
mode(AnimationMode) |
Breathe |
Animation style |
braille(bool) |
false |
Solid braille ⣿ fill instead of █ |
base(impl Into<Color>) |
DarkGray |
Dim resting color |
highlight(impl Into<Color>) |
Gray |
Peak brightness color |
block(Block) |
None |
Optional border container |
Shape-specific:
| Widget | Method | Default | Description |
|---|---|---|---|
SkeletonTable |
rows(u16) |
5 |
Number of visible rows |
SkeletonTable |
columns(&[Constraint]) |
[] |
Column width constraints |
SkeletonTable |
cell_widths(&[f32]) |
built-in pattern | Per-cell fill fractions, cycling across (row, col) |
SkeletonTable |
zebra(bool) |
true |
Alternating row brightness |
SkeletonList |
items(u16) |
5 |
Number of list items |
SkeletonList |
widths(&[f32]) |
built-in pattern | Per-item width fractions (cycles) |
SkeletonText |
line_widths(&[f32]) |
[1.0, 1.0, 0.8, 1.0, 0.6] |
Per-line width fractions (cycles) |
SkeletonBarChart |
bars(u16) |
6 |
Number of vertical bars |
SkeletonBarChart |
bar_width(u16) |
3 |
Width of each bar in cells |
SkeletonBarChart |
heights(&[f32]) |
built-in pattern | Per-bar height fractions (cycles) |
SkeletonHBarChart |
bars(u16) |
5 |
Number of horizontal bars |
SkeletonHBarChart |
bar_height(u16) |
1 |
Height of each bar in rows |
SkeletonHBarChart |
widths(&[f32]) |
built-in pattern | Per-bar width fractions (cycles) |
SkeletonBrailleBar |
bars(u16) |
3 |
Number of stacked braille bars |
SkeletonBrailleBar |
fills(&[f32]) |
built-in pattern | Per-bar fill fractions (cycles) |
SkeletonBrailleBar |
peak(f32) |
None |
Peak marker position (0.0..=1.0) |
SkeletonBrailleBar |
peak_color(Color) |
highlight | Color for the peak marker cell |
SkeletonBrailleBar |
empty(Color) |
Rgb(60,60,60) |
Color for unfilled cells |
SkeletonKvTable |
pairs(u16) |
5 |
Number of key-value pairs |
SkeletonKvTable |
key_width(u16) |
12 |
Fixed width of the key column |
SkeletonKvTable |
value_widths(&[f32]) |
built-in pattern | Per-pair value width fractions (cycles) |
SkeletonStreamingText |
lines(u16) |
5 |
Total lines to fill |
SkeletonStreamingText |
duration_ms(u64) |
3000 |
Milliseconds to complete the fill |
SkeletonStreamingText |
repeat(bool) |
false |
Loop the fill cycle |
SkeletonStreamingText |
line_widths(&[f32]) |
built-in pattern | Per-line width fractions (cycles, capped at 95%) |
SkeletonLineChart |
lines(u16) |
2 |
Number of overlapping wave traces |
SkeletonLineChart |
filled(bool) |
true |
Fill area below each wave |
SkeletonLineChart |
drift_ms(u64) |
None |
Override wave drift timestamp (freeze shape with 0) |
Examples
Interactive demo
All widget types in a 3×3 grid. [m] cycle animation modes, [b] toggle braille fill, [l] toggle skeleton↔data loop, [q] quit.
Widget pantry
This repo ships with tui-pantry integration — a widget isolation tool for ratatui. Browse every skeleton widget, all animation modes and fill variants, realistic use-case panes, and their props in an interactive viewer:
Or without cargo-pantry installed:

License
Dual licensed under MIT or Apache 2.0.
Contributing
Contributions are welcome. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you shall be dual licensed as above, without any additional terms or conditions.
See CONTRIBUTING.md for guidelines.