How to Use Ratatui: The Beginner’s Guide (First TUI, Widgets, Layouts, and the Event Arc)
CodexFuse Team · 2026-11-02T05:34:46.769Z · 15 min read min read
Learn how to use Ratatui, the standard Rust TUI library: cargo install, your first hello world on the 0.30 API, widgets, layouts, and the event loop.
How to Use Ratatui: The Beginner's Guide (First TUI, Widgets, Layouts, and the Event Arc)
Learning how to use ratatui is the fastest way for a Rust developer to start building real terminal user interfaces: dashboards, file browsers, git clients, and monitoring tools that live entirely in the terminal. Ratatui is the standard Rust TUI library, forked from the old tui-rs crate in 2023 and actively developed ever since. This ratatui tutorial is a complete ratatui for beginners guide: install with one cargo command, write a hello world app against the current 0.30 API, learn the widget lesson, split your screen with layouts, handle keyboard events, and structure a bigger app. It runs anywhere a terminal runs — which is also why it pairs well with the tmux beginner guide: tmux runs the terminal sessions, Ratatui builds the apps inside them.
What Is Ratatui, Exactly?
Ratatui is a Rust library for building fast, lightweight, and rich terminal user interfaces. You write Rust code that describes widgets — text panels, lists, tables, charts — and Ratatui draws them into your terminal window, redrawing every frame. It is explicit immediate-mode rendering with intermediate buffers: for each frame, your app renders all the widgets that are supposed to be part of the UI, in contrast to retained-mode style rendering where widgets are updated and automatically redrawn on the next frame.
Two facts set the mental model. First, Ratatui does not provide any input handling or event system — you bring a backend like Crossterm (the default, which works on most platforms), Termion, or Termwiz, and your code reads the keyboard events itself. Second, everything on screen is a widget following a builder-pattern API: you configure a widget, then render it into an area of the terminal with render_widget.
How Do You Use Ratatui?
The whole ratatui hello world journey fits in five steps:
- Install the Rust toolchain (1.88 or newer for Ratatui 0.30) and create a binary project with
cargo new. - Add the dependencies:
cargo add ratatui crossterm. - Initialize the terminal with
ratatui::init(), which enters the alternate screen and raw mode for you. - Draw in a loop: call
terminal.draw()to render your widgets each frame, and read crossterm events in the same loop. - Restore on exit: call
ratatui::restore()so the user's terminal returns to normal when the app quits.
That is the complete skeleton. Everything in the rest of this guide is growing that skeleton: richer widgets, layouts that split the screen, and real keyboard handling.
What Do You Need Before You Start?
You need a working Rust toolchain (version 1.88 or newer) and a terminal. Ratatui 0.30.2 requires Rust 1.88 or newer, so check with rustc --version before anything else. The official installation guide pairs cargo add ratatui crossterm with that version requirement, producing ratatui = "0.30.2" and crossterm = "0.29" in your Cargo.toml.
If you are setting up Rust on macOS for the first time, our Homebrew beginner guide walks through installing a package manager first. On any platform, the rustup installer from the Rust website is the supported path. No prior TUI experience is assumed — but you should be comfortable writing basic Rust: functions, structs, and match statements.
How Do You Install Ratatui?
You install Ratatui with one cargo command: cargo add ratatui crossterm. That single step covers both the library and its default backend.
cargo new my-tui cd my-tui cargo add ratatui crossterm cargo run
What each step does:
| Command | What it does |
|---|---|
cargo new my-tui | Creates a new binary Rust project |
cargo add ratatui crossterm | Adds Ratatui 0.30 and the Crossterm backend (currently 0.29) |
cargo run | Compiles and runs your first TUI |
If you want Termion instead of Crossterm as the backend, you swap the dependency line to add termion = "2" and configure Ratatui with the Termion backend. The library itself is MIT-licensed and open source, so there is no license cost to evaluate at any step.
How Do You Write Your First Ratatui App?
You initialize the terminal, draw widgets in a loop, and restore the terminal on exit. Here is the official 0.30 hello world from the Ratatui docs, verbatim — the exact code a beginner should start from:
use color_eyre::Result;
use crossterm::event::{self, Event};
use ratatui::{DefaultTerminal, Frame};
fn main() -> Result<()> {
color_eyre::install()?;
let terminal = ratatui::init(); // enters alternate screen + raw mode
let result = run(terminal);
ratatui::restore(); // leaves alternate screen / raw mode
result
}
fn run(mut terminal: DefaultTerminal) -> Result<()> {
loop {
terminal.draw(render)?;
if matches!(event::read()?, Event::Key(_)) {
break Ok(());
}
}
}
fn render(frame: &mut Frame) {
frame.render_widget("hello world", frame.area());
}
Three lines do the heavy lifting. ratatui::init() returns a DefaultTerminal that has already entered the alternate screen and raw mode, and it sets up a panic hook that restores the terminal if your app crashes. terminal.draw(render) runs your render function once per frame. ratatui::restore() leaves the alternate screen and raw mode so the user's shell comes back clean.
The official Hello Ratatui tutorial offers an even shorter form: the ratatui::run(app)? wrapper handles initialization, cleanup, and panic hooks around your app closure. And if you keep a project repo for this learning arc, our Git beginner guide covers initializing a repository before you start experimenting.
What Is the Widget Lesson?
The widget lesson is this: every visible piece of your UI is a widget, configured with a builder pattern and rendered into a rectangular area. Ratatui ships a set of built-in widgets that covers most cases, and you can implement your own for the rest. The classic teaching sequence is Block, Paragraph, List, Table — in that order.
| Widget | What it draws | Beginner use |
|---|---|---|
Block | A bordered box with an optional title | Wraps everything — panels, headers, footers |
Paragraph | Text, with wrapping, alignment, and scrolling | Help text, status messages, about screens |
List | A scrollable list of items with selection highlight | Menus, file lists, log viewers |
Table | Rows and columns with headers and widths | Process lists, dashboards, data views |
Gauge | A horizontal progress bar with a percentage label | Download progress, disk usage |
Sparkline | A tiny inline graph of numbers | CPU history, latency trends |
Every widget follows the same builder rhythm — default configuration, then chained methods to customize:
let block = Block::default()
.title("My App")
.borders(Borders::ALL);
frame.render_widget(block, frame.area());
Notice that the widget is consumed by render_widget along with the area to draw into. The docs use this Block example as the canonical builder-pattern demonstration: default config plus methods, then one render call.
How Do Ratatui Layouts Work?
Ratatui layouts split a rectangular area into smaller chunks using a Layout built from constraints, then you render one widget per chunk. The Layout object is the library's answer to "sidebar plus main panel plus status bar" — you declare how to divide the space, Ratatui computes the rectangles with a constraint solver (Kasuari, derived from Cassowary), and you draw into each piece.
| Constraint | Meaning | Use it for |
|---|---|---|
Constraint::Length(n) | Exactly n cells | Status bars, title rows of fixed height |
Constraint::Percentage(n) | n percent of the parent | Two-pane splits that track the window |
Constraint::Ratio(a, b) | a/b of the parent | Proportional columns like a 1:2 split |
Constraint::Fill(n) | Proportional share of leftover space | Letting panels absorb window resizing |
Constraint::Min(n) | At least n cells | Guaranteeing a panel stays usable |
Constraint::Max(n) | At most n cells | Capping a sidebar so it never grows wild |
When constraints compete, the solver resolves them in a fixed priority: Min > Max > Length > Percentage > Ratio > Fill. In practice, the beginner pattern is a vertical split with a fixed header and a fillable body, or a horizontal split for sidebar-plus-content. You choose the direction, declare the constraints in order, and the returned chunk array lines up with your constraint list.
How Do You Handle Keyboard Input and Quit?
You read crossterm events in your main loop and quit when the user presses q — Ratatui itself never handles input for you. The library explicitly provides no input handling and no event system; events arrive from whichever backend you chose, and your code decides what they mean.
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
fn handle_events(&mut self) -> io::Result<()> {
if let Event::Key(key) = event::read()? {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Char('q') => self.exit = true,
_ => {}
}
}
}
Ok(())
}
The official Counter App tutorial uses exactly this shape: an App struct with an exit flag, a while !self.exit loop calling terminal.draw() and self.handle_events(), and the KeyEventKind::Press check so a single press registers once instead of repeating. For loops that also need to do background work, event::poll(timeout) lets you wait for input with a timeout and redraw on a schedule.
Which Backend Should You Use?
Use Crossterm unless you have a specific reason not to — it is the default and works on most platforms. Ratatui is generic over backend implementations, and the docs list three: Crossterm (default), Termion, and Termwiz. Ratatui 0.30.2 also added a new ratatui-termina backend as a patch-release addition.
The practical rule is simple. Crossterm covers Windows, macOS, and Linux with one dependency, which is why every official tutorial and example uses it. Termion is Unix-only and a fit for projects that already depend on it. Termwiz is the alternative for terminal-emulator-adjacent work. If you are asking the question as a beginner, the answer is Crossterm — the backend is one line in Cargo.toml and never the hard part of a TUI.
How Do You Structure a Bigger Ratatui App?
For a bigger app, the official docs recommend picking one of three application patterns: the Elm architecture, the component architecture, or the Flux architecture. Ratatui itself is unopinionated about structure, but the "Application Patterns" page blesses these three for apps that outgrow a single render function.
The Elm pattern is the beginner-friendly one: split your app into a Model (state), an Update function (turns events into new state), and a View (renders state as widgets). The Counter App tutorial demonstrates exactly this shape with a tiny App struct holding a counter and an exit flag — increment on a key press, re-render every frame. The component architecture nests that idea into reusable components with their own state, and Flux routes events through a dispatcher into stores for the largest apps.
A practical growth path: start with the hello world loop, extract an App struct with state and a render method, then move each screen region into its own widget or component function. You will know you need the Elm split when your render function starts taking more than three parameters.
How Does Ratatui Compare to Textual?
Ratatui is the Rust ecosystem's immediate-mode TUI library; Textual is the Python ecosystem's TUI framework — pick by language, not by features. Textual builds apps in Python with a reactive App class, a compose() method that yields widgets, and TCSS stylesheets for layout, as its official docs demonstrate with a full calculator app. Ratatui builds apps in Rust with an immediate-mode render loop, builder-pattern widgets, and constraint layouts driven by your own crossterm event loop.
The honest one-line contrast the docs themselves draw is immediate mode versus retained mode: in Ratatui, your code re-renders every widget every frame; in retained-mode frameworks, widgets hold state and redraw themselves. If your project is Rust, Ratatui is the standard choice — this whole page covers it. If your project is Python, Textual is the equivalent lane, and it is not a rival to this page. (For completeness: community Python bindings for Ratatui exist via the ratatui_ffi C ABI, published on PyPI, but the Rust path is the native one.)
What Real Apps Are Built With Ratatui?
The official Ratatui showcase lists production TUIs including atuin, gitui, trippy, yazi, and bottom. That list matters because it proves the library is not a toy: atuin is a shell-history tool backed by SQLite, gitui is a full git client, trippy is a network diagnostic tool combining traceroute and ping, yazi is a blazing file manager, and bottom is a system monitor.
The showcase also includes xplr, joshuto, bandwhich, csvlens, dua, slumber, and taskwarrior-tui among many others — file managers, network tools, CSV viewers, and task managers all rendering in the same widget-and-layout vocabulary this guide teaches. When you finish your first hello world, picking one of these and reading its source is the fastest way to see how the patterns scale. And for a sibling in the Go ecosystem that scripts interactive terminal prompts instead of full apps, see our gum beginner guide — charm-ecosystem scripting versus Ratatui's full application framework.
What Are the 7 Beginner Mistakes?
| Mistake | Why it hurts | The fix |
|---|---|---|
Skipping ratatui::restore() | Your shell is left in raw mode with a broken display after the app exits | Use init() + restore(), or the ratatui::run() wrapper that handles both |
Reading keys without checking KeyEventKind::Press | One keypress registers multiple times (press + release + repeat) | Match only KeyEventKind::Press in your event handler |
Blocking forever on event::read() | The UI can never redraw on a timer or poll background work | Use event::poll(timeout) and loop with a deadline |
| Hardcoding layout sizes | The app breaks the moment the window resizes | Use constraints (Percentage, Fill, Min) instead of pixel math |
Copying 0.2x-era CrosstermBackend setup code | Older examples hand-roll raw mode and alternate screen; the 0.30 API does it for you | Pin to the 0.30 pattern: ratatui::init(), draw loop, ratatui::restore() |
| Rendering different widgets on different frames | Immediate mode means every frame must render everything — skipped widgets flicker or vanish | Render all visible widgets in every draw call |
| Drawing outside the area you were given | Widgets overflow into neighbors and corrupt the layout | Always render into the Rect your layout chunk provides |
Where Should You Go After This Guide?
After this guide, work through the official Recipes and Examples, then study one showcase app end to end. The Ratatui website ships a Recipes section with practical how-to guides for common patterns, an Examples collection demonstrating the library, and starter templates in a dedicated templates repository — the docs point beginners there right after the hello world.
Two practice projects cement the lesson: a todo list (List widget + add/remove on keypress + a status Bar) and a live dashboard (Gauge + Sparkline fed by event::poll timer redraws). Both force you to use widgets, layouts, and the event arc together. When you are ready to run your TUIs in a polished terminal, our Ghostty beginner guide covers a modern terminal emulator that renders this kind of interface cleanly.
Sources
- Ratatui crate documentation (0.30.0) — quickstart hello world, immediate rendering with intermediate buffers, init/restore pattern, widget builder API — https://docs.rs/ratatui/0.30.0/ratatui/
- Ratatui crate documentation (latest) — "forked from the tui-rs crate in 2023", MIT license, Crossterm default backend, Termion/Termwiz alternatives — https://docs.rs/crate/ratatui/latest
- Ratatui crate documentation (0.26.0) — "the library does not provide any input handling nor any event system" — https://docs.rs/ratatui/0.26.0/ratatui/index.html
- Ratatui homepage — "a Rust library for building fast, lightweight, and rich terminal user interfaces"; widget set (charts, sparklines, tables, gauges, scrollable lists, progress bars) — https://ratatui.rs/
- Ratatui installation guide —
cargo add ratatui crossterm, Ratatui 0.30.2 requires Rust 1.88+, backend options — https://ratatui.rs/installation/ - Ratatui 0.30.2 highlights — patch release: new ratatui-termina backend, scrollbar thumb overflow fix, wide-cell cleanup, Send on widget shadow effects — https://ratatui.rs/highlights/v0302/
- Ratatui Hello Ratatui tutorial —
ratatui::run(app)?managed init/cleanup wrapper — https://ratatui.rs/tutorials/hello-ratatui/ - Ratatui Counter App tutorial (error handling) — App-struct loop, quit on
KeyCode::Char('q')withKeyEventKind::Press— https://ratatui.rs/tutorials/counter-app/error-handling/ - Ratatui concepts: Layout — Constraint types (Length, Percentage, Ratio, Fill, Min, Max), solver priority Min > Max > Length > Percentage > Ratio > Fill — https://ratatui.rs/concepts/layout/
- Ratatui concepts: Application Patterns — the Elm Architecture, Component Architecture, Flux Architecture — https://ratatui.rs/concepts/application-patterns/
- Ratatui concepts: Rendering under the hood — immediate rendering versus retained mode — https://ratatui.rs/concepts/rendering/under-the-hood/
- Ratatui showcase: apps — atuin, gitui, trippy, yazi, bottom, xplr and others — https://ratatui.rs/showcase/apps/
- Ratatui GitHub repository — 23,143 stars, 819 forks, created 2023-02-12, MIT license — https://github.com/ratatui/ratatui
- Bright Coding blog, 2025-09-13 — independent Ratatui beginner walkthrough (first app, builder-pattern widgets) — https://www.blog.brightcoding.dev/2025/09/13/ratatui-building-rich-terminal-user-interfaces-in-rust
- AI Void field guide, 2026-05-04 — "Mastering Ratatui: A Zero-to-Advanced Guide" (pins the 0.26 API era) — https://media.aivoid.dev/pdfs/mastering-ratatui-a-zero-to-advanced-guide_20260504182303.pdf
- Textual documentation homepage — Python TUI framework: reactive App, compose() yielding widgets, TCSS stylesheets — https://textual.textualize.io/
- Ratatui Python bindings (PyPI) — zero-build Python bindings for ratatui_ffi, the C ABI for Ratatui — https://pypi.org/project/ratatui/
Frequently Asked Questions
Do I need to know Rust before learning Ratatui?
Yes — basic Rust. Ratatui is a Rust library, so you should be comfortable with functions, structs, enums, and match statements before starting. You do not need async Rust or advanced traits; the hello world loop uses plain synchronous code. If you are still learning Rust itself, finish the official Rust book's first chapters first.
Which Rust version does Ratatui 0.30 need?
Rust 1.88 or newer. The official installation guide pins that requirement alongside the cargo add ratatui crossterm command. Check your version with rustc --version and update with rustup if you are behind — older toolchains will refuse to compile the crate.
Do I need Crossterm if I am already using Ratatui?
Yes. Ratatui handles rendering only — it provides no input handling and no event system. Crossterm is the default backend that reads keyboard and mouse events and drives the terminal; every official tutorial pairs the two. Alternatives exist (Termion, Termwiz), but beginners should use Crossterm.
Can I use Ratatui with Python?
Not natively — Ratatui is a Rust library. Community Python bindings exist on PyPI built on the ratatui_ffi C ABI, but they are a thin wrapper around the Rust crate, not the supported path. If your project is Python-first, Textual is the ecosystem's native TUI framework; if it is Rust-first, Ratatui is the standard choice.
Why is my terminal broken after my Ratatui app exits?
Because the app exited without restoring the terminal — it is still in raw mode on the alternate screen. Always pair ratatui::init() with ratatui::restore(), or use the ratatui::run() wrapper that handles initialization, restoration, and panic hooks automatically. Typing reset and pressing Enter repairs a broken session.