Expand description
§tui
A lightweight, composable terminal UI library for building full-screen CLI apps in Rust.
Your app owns its event loop and state machine. The library provides composable building blocks: a Component trait for widgets, a diff-based Renderer, and RAII terminal management.
§Table of Contents
§Minimal app
A complete TUI app has four parts: a TerminalSession (raw mode guard), a Renderer (output), an event source, and a loop that wires them together.
use std::io;
use tui::{
Component, CrosstermEvent, Event, Frame, KeyCode, Line,
MouseCapture, Renderer, TerminalSession, Theme, ViewContext,
spawn_terminal_event_task, terminal_size,
};
// 1. Define your root component
struct Counter { count: i32 }
impl Component for Counter {
type Message = CounterMsg;
async fn on_event(&mut self, event: &Event) -> Option<Vec<CounterMsg>> {
if let Event::Key(key) = event {
match key.code {
KeyCode::Up => self.count += 1,
KeyCode::Down => self.count -= 1,
KeyCode::Char('q') => return Some(vec![CounterMsg::Quit]),
_ => return None,
}
return Some(vec![]);
}
None
}
fn render(&mut self, ctx: &ViewContext) -> Frame {
Frame::new(vec![
Line::styled("Counter (↑/↓, q to quit)", ctx.theme.muted()),
Line::new(format!(" {}", self.count)),
])
}
}
enum CounterMsg { Quit }
// 2. Set up terminal, renderer, and event source
#[tokio::main]
async fn main() -> io::Result<()> {
let size = terminal_size().unwrap_or((80, 24));
let mut renderer = Renderer::new(io::stdout(), Theme::default(), size);
let _session = TerminalSession::new(true, MouseCapture::Disabled)?;
let mut events = spawn_terminal_event_task();
let mut app = Counter { count: 0 };
renderer.render_frame(|ctx| app.render(ctx))?; // initial paint
// 3. Event loop
loop {
let Some(raw) = events.recv().await else { break };
if let CrosstermEvent::Resize(c, r) = &raw {
renderer.on_resize((*c, *r));
}
if let Ok(event) = Event::try_from(raw) {
if let Some(msgs) = app.on_event(&event).await {
for msg in msgs {
match msg {
CounterMsg::Quit => return Ok(()),
}
}
}
renderer.render_frame(|ctx| app.render(ctx))?;
}
}
Ok(())
}Dropping _session automatically restores the terminal (disables raw mode, bracketed paste, and mouse capture).
§How it works
crossterm::Event ──→ Event::try_from ──→ Component::on_event ──→ Vec<Message>
│ │
▼ ▼
Component::render parent handles messages
│
▼
Renderer::render_frame (diff → ANSI)spawn_terminal_event_task()reads raw crossterm events in a blocking tokio task.Event::try_fromfilters key releases and normalizes resize events.Component::on_eventreturnsNone(ignored),Some(vec![])(consumed), orSome(vec![msg])(messages for the parent).Component::renderreturns aFrame(lines + cursor) given aViewContext(size + theme).Renderer::render_framediffs against the previous frame and emits only changed ANSI sequences.
§Composing components
Nest components by owning them in your parent and delegating events:
use tui::{Component, Event, Frame, Layout, ViewContext, TextField, merge};
struct MyApp {
name: TextField,
path: TextField,
// ...
}
impl Component for MyApp {
type Message = ();
async fn on_event(&mut self, event: &Event) -> Option<Vec<()>> {
// Delegate to the focused child; merge results if needed
merge(
self.name.on_event(event).await,
self.path.on_event(event).await,
)
}
fn render(&mut self, ctx: &ViewContext) -> Frame {
// Stack child frames vertically
let mut layout = Layout::new();
layout.section(self.name.render(ctx).into_lines());
layout.section(self.path.render(ctx).into_lines());
layout.into_frame()
}
}Use FocusRing to track which child receives events and Layout to stack frames vertically.
§Built-in widgets
| Widget | Description |
|---|---|
TextField | Single-line text input |
NumberField | Numeric input (integer or float) |
Checkbox | Boolean toggle [x] / [ ] |
RadioSelect | Single-select radio buttons |
MultiSelect | Multi-select checkboxes |
SelectList | Scrollable list with selection |
Form | Multi-field tabbed form |
Panel | Bordered container |
Spinner | Animated progress indicator |
Combobox | Fuzzy-searchable picker (feature: picker) |
§Feature flags
| Feature | Description | Default |
|---|---|---|
syntax | Syntax highlighting, markdown rendering, diff previews via syntect | yes |
picker | Fuzzy combobox picker via nucleo | yes |
testing | Test utilities (TestTerminal, render_component, assert_buffer_eq) | no |
Disable defaults for a smaller dependency tree:
[dependencies]
tui = { version = "0.1", default-features = false }§License
MIT
Modules§
- test_
picker - testing
- Test utilities for components built with this crate.
Structs§
- Checkbox
- Boolean toggle rendered as
[x]/[ ]. - Combobox
- A fuzzy-searchable picker that filters items as the user types.
- Cursor
- Logical cursor position within a component’s rendered output.
- Diff
Line - A single line in a diff, tagged with its change type.
- Diff
Preview - A preview of changed lines for an edit operation.
- Focus
Ring - Tracks which child in a list of focusable items is currently focused.
- Form
- A multi-field form rendered as a tabbed pane with a virtual “Submit” tab.
- Form
Field - A single field within a
Form. - Frame
- Logical output from a
Component::rendercall: a vector ofLines plus aCursorposition. - Fuzzy
Matcher - Gallery
- KeyEvent
- Represents a key event.
- KeyEvent
State - Represents extra state about the key event.
- KeyModifiers
- Represents key modifiers (shift, control, alt, etc.).
- Layout
- Stacks content sections vertically with automatic cursor offset tracking.
- Line
- A single line of styled terminal output, composed of
Spans. - Mouse
Event - Represents a mouse event.
- Multi
Select - Multi-select from a list of options, rendered as checkboxes with a cursor.
- Number
Field - Numeric input field supporting integers or floats.
- Panel
- A bordered panel for wrapping content blocks with title/footer chrome.
- Radio
Select - Single-select from a list of options, rendered as radio buttons.
- Renderer
- Diff-based terminal renderer that efficiently updates only changed content.
- Select
List - A generic scrollable list with keyboard and mouse navigation.
- Select
Option - A single option in a selection list.
- Span
- A contiguous run of text sharing a single
Style. - Spinner
- Split
Diff Cell - One side of a split diff row.
- Split
Diff Row - A row in a side-by-side diff, pairing an old (left) line with a new (right) line.
- Split
Layout - Split
Panel - Split
Widths - Style
- Text styling: foreground/background colors and attributes (bold, italic, underline, dim, strikethrough).
- Syntax
Highlighter - Unified syntax-highlighting facade.
- Terminal
Session - RAII guard for terminal raw mode, bracketed paste, and mouse capture.
- Text
Field - Single-line text input with cursor tracking and navigation.
- Theme
- Semantic color palette for TUI rendering.
- View
Context - Environment passed to
Component::render: terminal size and theme.
Enums§
- Color
- Represents a color.
- Crossterm
Event - Represents an event.
- DiffTag
- Tag indicating the kind of change a diff line represents.
- Either
- Event
- Unified input events for
Component::on_event. - Focus
Outcome - The result of
FocusRing::handle_key. - Form
Field Kind - The widget type backing a
FormField. - Form
Message - Messages emitted by
Forminput handling. - Gallery
Message - KeyCode
- Represents a key.
- KeyEvent
Kind - Represents a keyboard event kind.
- Mouse
Capture - Mouse
Event Kind - A mouse event kind.
- Picker
Key - Picker
Message - Generic message type for picker components.
- Renderer
Command - Select
List Message - Theme
Build Error
Constants§
- BORDER_
H_ PAD - Width consumed by left (“│ “) and right (” │“) borders.
- BRAILLE_
FRAMES - GUTTER_
WIDTH - SEPARATOR
- SEPARATOR_
WIDTH
Traits§
- Component
- The core abstraction for all interactive widgets. Every built-in widget implements this trait, and parent components compose children through it.
- Searchable
- Select
Item
Functions§
- classify_
key - display_
width_ text - highlight_
diff - Render a diff preview with syntax-highlighted context/removed/added lines.
- merge
- Merge two event outcomes.
None(ignored) yields to the other. Messages are concatenated in order. - pad_
text_ to_ width - Pads
textwith trailing spaces to reachtarget_widthdisplay columns. Returns the original text unchanged if it already meets or exceeds the target. - render_
diff - Renders a diff preview, choosing split or unified based on terminal width and whether the diff has removals.
- render_
markdown - soft_
wrap_ line - spawn_
terminal_ event_ task - split_
blank_ panel - split_
render_ cell - terminal_
size - truncate_
line - Truncates a styled line to fit within
max_widthdisplay columns. - truncate_
text - Truncates text to fit within
max_widthdisplay columns, appending “…” if truncated. Returns the original string borrowed when no truncation is needed. - wrap_
selection - Wrapping navigation helper for selection indices.
deltaof -1 moves up, +1 moves down, wrapping at boundaries.