TUIs with ratatui
Build interactive terminal UIs with ratatui, the maintained fork of tui-rs.
Busca en todas las páginas de la documentación
Build interactive terminal UIs with ratatui, the maintained fork of tui-rs.
use crossterm::event::{self, Event, KeyCode};
use ratatui::{prelude::*, widgets::Paragraph};
fn main() -> anyhow::Result<()> {
let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stderr()))?;
terminal.clear()?;
loop {
terminal.draw(|f| {
let area = f.area();
f.render_widget(Paragraph::new("Press q to quit"), area);
})?;
if let Event::Key(key) = event::read()? {
if key.code == KeyCode::Char('q') { break; }
}
}
terminal.show_cursor()?;
Ok(())
}When to reach for this: Dashboards, log viewers, or wizards where line-based CLIs are insufficient.
use crossterm::event::{self, Event, KeyCode};
use ratatui::{
layout::{Constraint, Direction, Layout},
prelude::*,
widgets::{Block, Borders, List, ListItem},
};
struct App {
items: Vec<String>,
selected: usize,
}
impl App {
fn next(&mut self) {
self.selected = (self.selected + 1) % self.items.len();
}
}
fn main() -> anyhow::Result<()> {
let mut app = App {
items: vec!["alpha".into(), "beta".into(), "gamma".into()],
selected: 0,
};
let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stderr()))?;
loop {
terminal.draw(|f| {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(3), Constraint::Length(1)])
.split(f.area());
let items: Vec<ListItem> = app.items.iter()
.enumerate()
.map(|(i, s)| {
let style = if i == app.selected {
Style::default().bg(Color::Blue)
} else {
Style::default()
};
ListItem::new(s.as_str()).style(style)
})
.collect();
f.render_widget(
List::new(items).block(Block::default().borders(Borders::ALL).title("Items")),
chunks[0],
);
})?;
if let Event::Key(key) = event::read()? {
match key.code {
KeyCode::Char('q') => break,
KeyCode::Down | KeyCode::Char('j') => app.next(),
_ => {}
}
}
}
Ok(())
}What this demonstrates:
crosstermCrosstermBackend handles terminal raw modeParagraph, Table, Chart, Tabsevent::read() each framedraw is pure rendering| Widget | Use for |
|---|---|
List | Selectable menus |
Table | Tabular data |
Block | Titled panels with borders |
Gauge | Progress visualization |
ratatui::init() / restore() or a Drop guard.poll timeout. Fix: Use event::poll(Duration::from_millis(100)).Event::Resize and redraw.| Alternative | Use When | Don't Use When |
|---|---|---|
Line-based CLI + inquire | Simple prompts | Multi-panel dashboards |
dialoguer | Yes/no and select prompts | Complex layouts |
| Web UI | Rich graphics needed | SSH-only server environments |
ratatui is the actively maintained fork. New projects should use ratatui.
Yes. Run the event loop on a blocking thread or use tokio::select! with async channels for updates.
Extract rendering into functions taking Buffer and assert on cell content.
ratatui includes Chart for sparklines and line charts. For heavy data, consider exporting to a browser.
Use an enum for app state (List, Detail, Help) and match in draw.
Define a Theme struct with Style values. Support light/dark via flag.
Integrate arboard for copy/paste outside the basic TUI scope.
crossterm supports Windows terminals. Test on your target terminal emulator.
Launch TUI subcommand: myapp tui enters full-screen; default mode stays line-based.
Provide equivalent non-TUI commands (--plain, --json) for screen readers and scripting.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Revisado por Chris St. John·Última actualización: 16 jul 2026