Skip to content

Hello world

This tutorial will lead you through creating a simple “Hello World” TUI app that displays some text in the middle of the screen and waits for the user to press q to exit. It demonstrates the necessary tasks that any application developed with Ratatui needs to undertake. We assume you have a basic understanding of the terminal, and have a text editor or Rust IDE. If you don’t have a preference, VSCode makes a good default choice.

You’re going to build the following:

hello-ratatui

The full code for this tutorial is available to view at https://github.com/ratatui/ratatui-website/tree/main/code/hello-ratatui

Install Rust

The first step is to install Rust. See the Installation section of the official Rust Book for more information. Most people use rustup, a command line tool for managing Rust versions and associated tools.

Once you’ve installed Rust, verify it’s installed by running:

check rust version
rustc --version

You should see output similar to the following (the exact version, date and commit hash will vary):

rustc 1.81.0 (eeb90cda1 2024-09-04)

Install Cargo generate

Cargo generate is a tool that makes it possible to create templates for rust projects.

Install it by running the following command:

Terminal window
cargo install cargo-generate

See https://cargo-generate.github.io/cargo-generate/installation.html for other approaches to installing cargo-generate.

Create a New Project

Let’s create a new Rust project. In the terminal, navigate to a folder where you will store your projects and run the following command to generate a new app using the simple ratatui template. (You can find more information about this template in the Simple Template README)

create new rust project
cargo generate ratatui/templates simple

You will be prompted for a project name to use. Enter hello-ratatui.

⚠️ Favorite `ratatui/templates` not found in config, using it as a git repository: https://github.com/ratatui/templates.git
🤷 Project Name: hello-ratatui
🔧 Destination: /Users/joshka/local/ratatui-website/code/tutorials/hello-ratatui ...
🔧 project-name: hello-ratatui ...
🔧 Generating template ...
🔧 Moving generated files into: `/Users/joshka/local/ratatui-website/code/tutorials/hello-ratatui`...
🔧 Initializing a fresh Git repository
✨ Done! New project created /Users/joshka/local/ratatui-website/code/tutorials/hello-ratatui

The cargo generate command creates a new folder called hello-ratatui with a basic binary application in it. If you examine the folders and files created this will look like:

hello-ratatui/
├── src/
│ ├── app.rs
│ └── main.rs
├── Cargo.toml
├── LICENSE
└── README.md

The Cargo.toml file is filled with some default values and the necessary dependencies (Ratatui and Crossterm), and one useful dependency (Color-eyre) for nicer error handling.

cargo.toml
[package]
name = "hello-ratatui"
version = "0.1.0"
authors = ["Josh McKinney <joshka@users.noreply.github.com>"]
license = "MIT"
edition = "2021"
[dependencies]
crossterm = "0.28.1"
ratatui = "0.28.1"
color-eyre = "0.6.3"

The generate command created a default main.rs that runs the app:

main.rs
pub use app::App;
pub mod app;
fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
let terminal = ratatui::init();
let result = App::new().run(terminal);
ratatui::restore();
result
}

And an App struct in app.rs that contains the main logic:

app.rs
use color_eyre::Result;
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use ratatui::{
style::Stylize,
text::Line,
widgets::{Block, Paragraph},
DefaultTerminal, Frame,
};
#[derive(Debug, Default)]
pub struct App {
/// Is the application running?
running: bool,
}

The App implementation contains methods to create the app and run the main application loop. The loop runs until the running field is set to false.

app.rs
impl App {
/// Construct a new instance of [`App`].
pub fn new() -> Self {
Self::default()
}
/// Run the application's main loop.
pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
self.running = true;
while self.running {
terminal.draw(|frame| self.draw(frame))?;
self.handle_crossterm_events()?;
}
Ok(())
}
}

The draw method controls what is drawn to the screen. The template draws a paragraph of text surrounded by a borderd block with a title to the entire screen (frame) area.

app.rs
impl App {
/// Renders the user interface.
///
/// This is where you add new widgets. See the following resources for more information:
/// - <https://docs.rs/ratatui/latest/ratatui/widgets/index.html>
/// - <https://github.com/ratatui/ratatui/tree/master/examples>
fn draw(&mut self, frame: &mut Frame) {
let title = Line::from("Ratatui Simple Template")
.bold()
.blue()
.centered();
let text = "Hello, Ratatui!\n\n\
Created using https://github.com/ratatui/templates\n\
Press `Esc`, `Ctrl-C` or `q` to stop running.";
frame.render_widget(
Paragraph::new(text)
.block(Block::bordered().title(title))
.centered(),
frame.area(),
)
}
}

The app has methods for interacting with the user. These set the running field to false when the user presses q, Esc, or Ctrl+C

app.rs
impl App {
/// Reads the crossterm events and updates the state of [`App`].
///
/// If your application needs to perform work in between handling events, you can use the
/// [`event::poll`] function to check if there are any events available with a timeout.
fn handle_crossterm_events(&mut self) -> Result<()> {
match event::read()? {
// it's important to check KeyEventKind::Press to avoid handling key release events
Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_event(key),
Event::Mouse(_) => {}
Event::Resize(_, _) => {}
_ => {}
}
Ok(())
}
/// Handles the key events and updates the state of [`App`].
fn on_key_event(&mut self, key: KeyEvent) {
match (key.modifiers, key.code) {
(_, KeyCode::Esc | KeyCode::Char('q'))
| (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(),
// Add other key handlers here.
_ => {}
}
}
/// Set running to false to quit the application.
fn quit(&mut self) {
self.running = false;
}
}

Let’s build and execute the project. Run:

run the app
cd hello-ratatui
cargo run

You should see the following build messages:

❯ cargo run
Compiling tracing v0.1.40
Compiling tracing-subscriber v0.3.18
Compiling ahash v0.8.11
Compiling memchr v2.7.4
Compiling hashbrown v0.14.5
Compiling object v0.32.2
Compiling lru v0.12.4
Compiling ratatui v0.28.1
Compiling tracing-error v0.2.0
Compiling color-spantrace v0.2.1
Compiling backtrace v0.3.71
Compiling color-eyre v0.6.3
Compiling hello-ratatui v0.1.0 (/Users/joshka/local/ratatui-website/code/tutorials/hello-ratatui)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 5.44s
Running `/Users/joshka/local/ratatui-website/target/debug/hello-ratatui`

And then the following:

You should then see a TUI app with Hello Ratatui! (press 'q' to quit) show up in your terminal as a TUI app.

hello

You can press q to exit and go back to your terminal as it was before.

Congratulations! 🎉

You have written a “hello world” terminal user interface with Ratatui. The next sections will go into more detail about how Ratatui works.

Next Steps

The next tutorial, Counter App, introduces some more interactivity, and a more robust approach to arranging your application code.