Ratatui 0.21.0 highlights


New backend: termwiz

ratatui supports a new backend called termwiz which is a “Terminal Wizardry” crate that powers

To use it, enable the termwiz feature in Cargo.toml:

version = "0.21.0"
features = ["termwiz"]
default-features = false

Then you can utilize TermwizBackend object for creating a terminal. Here is a simple program that
shows a text on the screen for 5 seconds using ratatui + termwiz:

use ratatui::{backend::TermwizBackend, widgets::Paragraph, Terminal};
use std::{
    time::{Duration, Instant},

fn main() -> Result<(), Box<dyn Error>> {
    let backend = TermwizBackend::new()?;
    let mut terminal = Terminal::new(backend)?;

    let now = Instant::now();
    while now.elapsed() < Duration::from_secs(5) {
        terminal.draw(|f| f.render_widget(Paragraph::new("termwiz example"), f.size()))?;


New widget: Calendar

A calendar widget has been added which was originally a part of the
extra-widgets repository.

Since this new widget depends on time crate, we gated it behind widget-calendar feature to avoid
an extra dependency:

version = "0.21.0"
features = ["widget-calendar"]

Here is the example usage:

    time::Date::from_calendar_date(2023, time::Month::January, 1).unwrap(),

Results in:

     January 2023
 Su Mo Tu We Th Fr Sa
  1  2  3  4  5  6  7
  8  9 10 11 12 13 14
 15 16 17 18 19 20 21
 22 23 24 25 26 27 28
 29 30 31  1  2  3  4

New widget: Circle

Circle widget has been added with the use-case of showing an accuracy radius on the world map.

Here is an example of how to use it with Canvas:

    .paint(|ctx| {
        ctx.draw(&Circle {
            x: 5.0,
            y: 2.0,
            radius: 5.0,
            color: Color::Reset,
    .x_bounds([-10.0, 10.0])
    .y_bounds([-10.0, 10.0]),

Results in:

⢸⡁  ⡧

Inline Viewport

This was a highly requested feature and the original implementation was done by
@fdehau himself. Folks at Atuin completed the
implementation and we are happy to finally have this incorporated in the new release!

An inline viewport refers to a rectangular section of the terminal window that is set aside for
displaying content.

In the repository, there is an example that simulates downloading multiple files in parallel:

Block: title on bottom

Before you could only put the title on the top row of a Block. Now you can put it on the bottom
row! Revolutionary.

For example, place the title on the bottom and center:

            .title(Span::styled("Title", Style::default()))

Results in:

│       ratatui       │
│                     │

Block: support adding padding

If we want to render a widget inside a Block with a certain distance from its borders, we need to
create another Layout element based on the outer Block, add a margin and render the Widget
into it. Adding a padding property on the block element skips the creation of this second Layout.

This property works especially when rendering texts, as we can just create a block with padding and
use it as the text wrapper:

let block = Block::default()
    .padding(Padding::new(1, 1, 2, 2));
let paragraph = Paragraph::new("example paragraph").block(block);
f.render_widget(paragraph, area);

Rendering another widget should be easy too, using the .inner method:

let block = Block::default().borders(Borders::ALL).padding(Padding {
    left: todo!(),
    right: todo!(),
    top: todo!(),
    bottom: todo!(),
let inner_block = Block::default().borders(Borders::ALL);
let inner_area = block.inner(area);

f.render_widget(block, area);
f.render_widget(inner_block, inner_area);
f.render_widget(paragraph, area);

Text: display secure data

A new type called Masked is added for text-related types for masking data with a mask character.
The example usage is as follows:

    Span::raw("Masked text: "),
        Masked::new("password", '*'),

Results in:

Masked text: ********

border! macro

A border! macro has been added that takes TOP, BOTTOM, LEFT, RIGHT, and ALL and returns
a Borders object.

An empty border!() call returns NONE.

For example:

border!(LEFT, RIGHT)

This is gated behind a macros feature flag to ensure short build times. To enable it, update
Cargo.toml as follows:

version = "0.21.0"
features = ["macros"]

Going forward, we will most likely put the new macros behind macros feature as well.

Color: support conversion from String

Have you ever needed this conversion?

"black" => Color::Black,
"red" => Color::Red,
"green" => Color::Green,
// etc.

Don’t worry, we got you covered:

Color::from_str("lightblue") // Color::LightBlue
Color::from_str("10")        // Color::Indexed(10)
Color::from_str("#FF0000")   // Color::Rgb(255, 0, 0)


Line is a significantly better name over Spans as the plural causes confusion and the type
really is a representation of a line of text made up of spans.

So, Spans is renamed as Line and a deprecation notice has been added.

for more discussion.

Other features

  • List now has a len() method for returning the number of items
  • Sparkline now has a direction() method for specifying the render direction (left to right /
    right to left)
  • Table and List states now have offset() and offset_mut() methods
  • Expose the test buffer (TestBackend) with Display implementation

New apps

Here is the list of applications that has been added:

  • oxycards: quiz card application built within the
  • twitch-tui: twitch chat in the terminal.
  • tenere: TUI interface for LLMs.

Also, we moved APPS.md file to the
Wiki so check it out for more
applications built with ratatui!

Migration from tui-rs

We put together a migration guide at the Wiki:
Migrating from TUI

Also, the minimum supported Rust version is 1.65.0


Any contribution is highly appreciated! There are
contribution guidelines for
getting started.

Feel free to submit issues and throw in

If you are having a problem with ratatui or want to contribute to the project or just want to
chit-chat, feel free to join our Discord server!