rs-ratatui-crate

Ratatui is an immediate-mode Rust library for building terminal UIs. It renders the entire UI each frame from application state — there is no persistent widget tree. The default backend is Crossterm.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "rs-ratatui-crate" with this command: npx skills add padparadscho/skills/padparadscho-skills-rs-ratatui-crate

Ratatui

Ratatui is an immediate-mode Rust library for building terminal UIs. It renders the entire UI each frame from application state — there is no persistent widget tree. The default backend is Crossterm.

  • Crate: ratatui = "0.30"

  • Docs: https://docs.rs/ratatui/latest/ratatui/

  • MSRV: 1.86.0 (Rust 2024 edition)

  • Widget reference: Read references/widgets.md for built-in widget details, styling, and custom widget implementation

  • Architecture patterns: Read references/architecture.md for TEA, component, and monolithic patterns, event handling, layout, state management, and testing

Quick Start

Minimal app with ratatui::run() (v0.30+)

use ratatui::{widgets::{Block, Paragraph}, style::Stylize};

fn main() -> Result<(), Box<dyn std::error::Error>> { ratatui::run(|terminal| { loop { terminal.draw(|frame| { let greeting = Paragraph::new("Hello, Ratatui!") .centered() .yellow() .block(Block::bordered().title("Welcome")); frame.render_widget(greeting, frame.area()); })?; if crossterm::event::read()?.is_key_press() { break Ok(()); } } }) }

ratatui::run() calls init() before and restore() after the closure — handles terminal setup/teardown automatically.

App with init() /restore() (manual control)

fn main() -> Result<()> { color_eyre::install()?; let mut terminal = ratatui::init(); let result = run(&mut terminal); ratatui::restore(); result }

fn run(terminal: &mut ratatui::DefaultTerminal) -> Result<()> { loop { terminal.draw(|frame| { /* render widgets */ })?; if let Event::Key(key) = crossterm::event::read()? { if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { break; } } } Ok(()) }

Cargo.toml

[dependencies] ratatui = "0.30" crossterm = "0.29" color-eyre = "0.6"

Core Concepts

Rendering

Immediate-mode: call terminal.draw(|frame| { ... }) each tick. Build widgets from state and render — no retained widget tree.

terminal.draw(|frame| { frame.render_widget(some_widget, frame.area()); frame.render_stateful_widget(stateful_widget, area, &mut state); })?;

Layout

Use Layout to split areas with constraints. Prefer areas() for destructuring (v0.28+):

let [header, body, footer] = Layout::vertical([ Constraint::Length(3), Constraint::Min(0), Constraint::Length(1), ]).areas(frame.area());

Centering with Rect::centered() (v0.30+):

let popup_area = frame.area() .centered(Constraint::Percentage(60), Constraint::Percentage(40));

Or with Flex::Center :

let [area] = Layout::horizontal([Constraint::Length(40)]) .flex(Flex::Center) .areas(frame.area());

Constraint types: Length(n) , Min(n) , Max(n) , Percentage(n) , Ratio(a, b) , Fill(weight) .

Widgets

All widgets implement Widget trait (fn render(self, area: Rect, buf: &mut Buffer) ). Stateful widgets use StatefulWidget with an associated State type.

Built-in: Block , Paragraph , List , Table , Tabs , Gauge , LineGauge , BarChart , Chart , Canvas , Sparkline , Scrollbar , Calendar , Clear .

Text primitives: Span , Line , Text — all implement Widget .

See references/widgets.md for full API details.

Event Handling

Use Crossterm for input. Always check KeyEventKind::Press :

use crossterm::event::{self, Event, KeyCode, KeyEventKind};

if let Event::Key(key) = event::read()? { if key.kind == KeyEventKind::Press { match key.code { KeyCode::Char('q') => should_quit = true, KeyCode::Up | KeyCode::Char('k') => scroll_up(), KeyCode::Down | KeyCode::Char('j') => scroll_down(), _ => {} } } }

Terminal Setup & Panic Handling

With ratatui::run() (simplest, v0.30+):

fn main() -> Result<(), Box<dyn std::error::Error>> { ratatui::run(|terminal| { /* app loop */ }) }

With color-eyre panic hook (recommended for init() /restore() ):

fn install_hooks() -> Result<()> { let (panic_hook, eyre_hook) = color_eyre::config::HookBuilder::default().into_hooks(); let panic_hook = panic_hook.into_panic_hook(); std::panic::set_hook(Box::new(move |info| { ratatui::restore(); panic_hook(info); })); eyre_hook.install()?; Ok(()) }

Architecture

Choose based on complexity. See references/architecture.md for full patterns with code.

Complexity Pattern When

Simple Monolithic Single-screen, few key bindings, no async

Medium TEA (The Elm Architecture) Multiple modes, form-like interaction

Complex Component Multi-panel, reusable panes, plugin-like

TEA (The Elm Architecture) — Summary

struct Model { counter: i32, running: bool }

enum Message { Increment, Decrement, Quit }

fn update(model: &mut Model, msg: Message) { match msg { Message::Increment => model.counter += 1, Message::Decrement => model.counter -= 1, Message::Quit => model.running = false, } }

fn view(model: &Model, frame: &mut Frame) { let text = format!("Counter: {}", model.counter); frame.render_widget(Paragraph::new(text), frame.area()); }

Common Patterns

List Navigation with Selection

let mut list_state = ListState::default().with_selected(Some(0));

// Update match key.code { KeyCode::Up => list_state.select_previous(), KeyCode::Down => list_state.select_next(), _ => {} }

// Render let list = List::new(items) .block(Block::bordered().title("Items")) .highlight_style(Style::new().reversed()) .highlight_symbol(Line::from(">> ").bold()); frame.render_stateful_widget(list, area, &mut list_state);

Popup Overlay

fn render_popup(frame: &mut Frame, title: &str, content: &str) { let area = frame.area() .centered(Constraint::Percentage(60), Constraint::Percentage(40)); frame.render_widget(Clear, area); let popup = Paragraph::new(content) .block(Block::bordered().title(title).border_type(BorderType::Rounded)) .wrap(Wrap { trim: true }); frame.render_widget(popup, area); }

Tabbed Interface

let titles = vec!["Tab1", "Tab2", "Tab3"]; let tabs = Tabs::new(titles) .block(Block::bordered()) .select(selected_tab) .highlight_style(Style::new().bold().yellow()); frame.render_widget(tabs, tabs_area);

Custom Widget

struct StatusBar { message: String }

impl Widget for StatusBar { fn render(self, area: Rect, buf: &mut Buffer) { Line::from(self.message) .style(Style::new().bg(Color::DarkGray).fg(Color::White)) .render(area, buf); } }

// Implement for reference to avoid consuming the widget: impl Widget for &StatusBar { fn render(self, area: Rect, buf: &mut Buffer) { Line::from(self.message.as_str()) .style(Style::new().bg(Color::DarkGray).fg(Color::White)) .render(area, buf); } }

Text Input with tui-input

[dependencies] tui-input = "0.11"

use tui_input::Input; use tui_input::backend::crossterm::EventHandler;

let mut input = Input::default();

// In event handler: input.handle_event(&crossterm::event::Event::Key(key));

// In render: let width = area.width.saturating_sub(2) as usize; let scroll = input.visual_scroll(width); let input_widget = Paragraph::new(input.value()) .scroll((0, scroll as u16)) .block(Block::bordered().title("Search")); frame.render_widget(input_widget, area); frame.set_cursor_position(Position::new( area.x + (input.visual_cursor().max(scroll) - scroll) as u16 + 1, area.y + 1, ));

Key Conventions

  • Always restore terminal — even on panic. Use ratatui::run() or install a panic hook

  • Check KeyEventKind::Press on all key events

  • Use Block::bordered() as standard container

  • Prefer Layout::vertical/horizontal([...]).areas(rect) over .split(rect)

  • Use Clear widget before rendering popups/overlays

  • Implement Widget for &MyType when the widget should not be consumed on render

  • Use ListState , TableState , ScrollbarState for scroll/selection tracking

  • Prefer color-eyre for error handling in TUI apps

  • Use Rect::centered() (v0.30+) for centering layouts instead of double Flex::Center

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

js-gnome-extensions

No summary provided by upstream source.

Repository SourceNeeds Review
General

js-gnome-apps

No summary provided by upstream source.

Repository SourceNeeds Review
General

rs-soroban-sdk

No summary provided by upstream source.

Repository SourceNeeds Review
General

js-stellar-sdk

No summary provided by upstream source.

Repository SourceNeeds Review