A Rust TUI framework inspired by Python Textual, delivering modern terminal interfaces with CSS styling, reactive state, and rich widgets.
| Widget | Description |
|---|---|
Button |
Clickable with 3D depth, press animation |
Input |
Single-line text field with clipboard (Ctrl+C/V), text selection, validation states |
TextArea |
Multi-line editor with selection, clipboard, scroll |
Checkbox |
Toggle with checked/unchecked/indeterminate states |
Switch |
Animated pill-shaped sliding toggle |
RadioButton / RadioSet |
Mutually exclusive option groups |
Select |
Dropdown overlay with search filtering |
ListView |
Scrollable item list with keyboard navigation |
DataTable |
Columns, zebra striping, sortable headers, cursor row |
Tree / TreeView |
Hierarchical collapsible nodes |
Tabs / TabbedContent |
Tab bar with animated underline indicator |
Markdown |
Rendered markdown with headings, lists, code blocks |
Log |
Scrollable append-only message log |
ProgressBar |
Determinate progress with half-block fill |
Sparkline |
Braille-character mini charts |
Collapsible |
Expandable/collapsible sections |
Placeholder |
Quadrant cross-hatch placeholder |
ScrollView |
Scrollable container with eighth-block scrollbar |
Header / Footer |
App chrome with key badges |
Horizontal / Vertical |
Layout containers |
ContextMenu |
Right-click floating overlay menus |
CommandPalette |
Ctrl+P command discovery overlay |
Label |
Static text display |
RichLog |
Scrollable rich-text log with styled lines |
LoadingIndicator |
Animated spinner overlay on any widget |
Toast |
Stacked transient notifications with severity levels |
- CSS/TCSS engine -- type, class, and ID selectors with cascade and specificity
- Theme variables --
$primary,$surface,$accent, with shade generation ($primary-lighten-2,$accent-darken-1) - 7 built-in themes -- textual-dark, textual-light, tokyo-night, nord, gruvbox, dracula, catppuccin
- Runtime theme switching -- Ctrl+T to cycle themes
- 8 border styles -- ascii, blank, double, heavy, inner, outer, round, tall (McGugan Box)
- Box model -- padding, margin, min/max width/height, flex grow
- Pseudo-classes --
:focus,:hoverwith automatic visual feedback - Custom themes -- override any variable via user CSS
- Flexbox via Taffy --
layout-direction: vertical | horizontal,flex-grow,align-items,justify-content - Dock --
dock: top | bottom | left | rightfor fixed chrome - Responsive -- recomputes on terminal resize
- Mouse -- click, hover tracking, scroll wheel, right-click context menus
- Mouse capture stack -- push/pop mouse-enabled state; Shift+click bypasses capture for native text selection
- Keyboard -- key bindings, Tab/Shift+Tab focus cycling, Ctrl+C/X/V clipboard
- Focus system -- visible focus indicators with border color changes
Reactive<T>-- property wrapper that triggers re-renders on change- Signals -- built on
reactive_graph(from Leptos);ArcRwSignal/ArcMemo - Message passing -- typed async messages bubble up the widget tree
- Worker API --
run_worker()andrun_worker_with_progress()for async tasks off the UI thread - Command palette -- register commands via
CommandRegistry, invoke with Ctrl+P
- Tween system -- easing functions (linear, ease-in-out-cubic, ease-out-cubic)
- Built-in animations -- Switch toggle slide, Tab underline slide
- 30fps render tick -- ratatui diff makes unchanged frames nearly free
- Sub-cell graphics -- half-block, eighth-block, quadrant, and braille characters
- McGugan Box borders -- thin tall borders using Unicode block elements
- Terminal capability detection -- color depth, Unicode support auto-detected
TestApp-- headless application harness, no terminal neededPilot-- simulated keyboard/mouse input for integration tests- Snapshot testing -- insta-based plain-text buffer snapshots
- 21 visual regression tests included
- Windows 10+, macOS, Linux
- ratatui 0.30 + crossterm 0.29 backend
- Stable Rust (no nightly required), MSRV 1.88
cargo add textual-rsOr add manually to Cargo.toml:
[dependencies]
textual-rs = "0.3"use textual_rs::{App, Widget, Label, Button, Header, Footer};
use textual_rs::widget::context::AppContext;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
struct MyScreen;
impl Widget for MyScreen {
fn widget_type_name(&self) -> &'static str { "MyScreen" }
fn compose(&self) -> Vec<Box<dyn Widget>> {
vec![
Box::new(Header::new("My App")),
Box::new(Label::new("Hello, textual-rs!")),
Box::new(Button::new("Click Me")),
Box::new(Footer),
]
}
fn render(&self, _ctx: &AppContext, _area: Rect, _buf: &mut Buffer) {}
}
fn main() -> anyhow::Result<()> {
let mut app = App::new(|| Box::new(MyScreen))
.with_css("MyScreen { layout-direction: vertical; background: $background; color: $foreground; }");
app.run()
}Screen {
background: $background;
color: $foreground;
}
Button {
border: inner;
min-width: 16;
height: 3;
background: $surface;
color: $foreground;
}
Button.primary {
background: $primary;
color: #ffffff;
}
*:focus {
border: tall $accent;
}cargo run --example demo # Widget showcase (4 tabs)
cargo run --example irc_demo # IRC client demo
cargo run --example tutorial_01_hello # Hello world
cargo run --example tutorial_02_layout # Layout system
cargo run --example tutorial_03_events # Event handling
cargo run --example tutorial_04_reactive # Reactive state
cargo run --example tutorial_05_workers # Background workers- User Guide -- complete walkthrough
- CSS Reference -- all properties, selectors, theme variables
MIT