Skip to content

v0.30.1

animation

We are excited to announce Ratatui 0.30.1! 🐁🚀🌕

See the changelog for the full list of changes. See the breaking changes for this release here.

Block widget now supports rendering shadows!

You can use the new Block::shadow(...) method to add a shadow effect to any block, with customizable presets and options for symbols, colors, and offsets.

use ratatui::layout::Offset;
use ratatui::style::Stylize;
use ratatui::widgets::{Block, Shadow};
let popup = Block::bordered().title("Popup").shadow(
Shadow::dark_shade()
.black()
.on_white()
.offset(Offset::new(2, 1)),
);

shadow

Canvas and Chart widgets now support filled area rendering which helps with visualizing trends and magnitudes in your data!

  • In Canvas, use FilledLine.
  • In Chart, use GraphType::Area and set the baseline via Dataset::fill_to_y(f64).
// In Canvas
use ratatui::widgets::canvas::FilledLine;
Canvas::default()
.paint(|ctx| {
ctx.draw(&FilledLine::new(0.0, 0.0, 10.0, 5.0, 0.0, Color::Red));
});
// In Chart
let dataset = Dataset::default()
.data(&data)
.graph_type(GraphType::Area)
.fill_to_y(0.0); // fill to y = 0
Chart::new(vec![dataset]);

изображение

изображение

Ratatui now exposes CellDiffOption, which gives widgets more control over how a cell is treated during buffer diffing. This is especially useful for cells that contain ANSI or OSC escape sequences, where the bytes stored in a cell do not necessarily match what is actually displayed.

Take \x1b[31mred\x1b[0m for example, an ANSI escape sequence that renders the text “red” in red color.

This is what is actually visible on screen:

0 1 2 r e d 0

But the buffer may store the whole ANSI sequence in a single cell:

0 1 2 " x1b[31mred x1b[0m 0

In this case, diffing has to look at the stored symbol and infer that it should be treated as a 3-width cell for diffing purposes. That’s where you can use CellDiffOption::ForcedWidth(3) to have the correct diffing behavior.

The other variants are:

  • CellDiffOption::None: no special diff option; use normal diffing, including computing width from the cell’s symbol.
  • CellDiffOption::Skip: skip this cell during diffing.
  • CellDiffOption::ForcedWidth(width): use the provided width for diffing instead of the symbol’s computed width.
  • CellDiffOption::AlwaysUpdate: always update this cell when diffing, bypassing the equality check against the previous buffer.

We have introduced a CellWidth trait for cell width computation, implemented for &str and Cell.

This trait provides a cell_width() method that returns the display width in terminal cells, taking into account diff options such as CellDiffOption::ForcedWidth (See Cell Diff Options above).

use core::num::NonZeroU16;
use ratatui::buffer::{Cell, CellDiffOption, CellWidth};
let text_width = "".cell_width(); // 2
let normal_cell = Cell::new("");
let normal_width = normal_cell.cell_width(); // 2
let mut forced_cell = Cell::new("a");
forced_cell.set_diff_option(CellDiffOption::ForcedWidth(
NonZeroU16::new(3).unwrap(),
));
let forced_width = forced_cell.cell_width(); // 3

The main benefit is having one Ratatui-native API for terminal width calculation that works consistently across both strings and buffer cells.

If you use Ratatui in a no_std environment (such as embedded), you can now enable the layout cache to improve performance. It can significantly reduce CPU usage and increase frame rates by caching the results of layout calculations, which can be expensive to compute on every frame.

You can enable it via the layout-cache feature:

ratatui = { version = "0.30.1", default-features = false, features = ["layout-cache"] }

Terminal::flush no longer allocates a temporary Vec<(u16, u16, &Cell)> for each frame when diffing buffers. Instead, diffing now uses an iterator-based approach via BufferDiff, yielding cell updates one at a time as they are processed.

This avoids building a short-lived contiguous allocation for every frame, which reduces memory pressure and makes diffing more suitable for constrained environments.

We have added a new example showcasing a 3D visualization of a volatility surface!

It demonstrates how to implement perspective projection in a terminal and shows advanced use of the Canvas widget with Marker::Braille. It also includes interactive rotation and zoom controls for exploring the 3D surface.

volatility-surface

To try it out yourself:

Terminal window
git clone https://github.com/ratatui/ratatui && cd ratatui/
cargo run -p volatility-surface

Canvas and Chart widgets now support custom marker shapes via symbols::Marker::Custom(char). This allows you to use any character while visualizing data:

use ratatui::{symbols::Marker, widgets::canvas::Canvas};
let canvas = Canvas::default()
.marker(Marker::Custom('+')); // Use '+' as the marker symbol
use ratatui::{symbols::Marker, widgets::{Chart, Dataset}};
let chart = Chart::new(vec![
Dataset::default()
.marker(Marker::Custom('x')) // Use 'x' as the marker symbol
.data(&[(0.0, 1.0), (1.0, 2.0)]),
]);

Table cells can span multiple columns with Cell::column_span(n), letting a single cell render across adjacent columns and the spacing between them.

use ratatui::widgets::{Cell, Row, Table};
let rows = vec![
Row::new(vec![
Cell::new("Name").column_span(2),
Cell::new("Score"),
]),
Row::new(vec![
Cell::new("Alice"),
Cell::new("A."),
Cell::new("42"),
]),
];
let table = Table::new(rows, [5, 5, 5]);

Results in:

0 1 2 Name Score Alice A. 42 0 1

Built-in widgets now implement AsRef to allow using them in generic contexts.

This means code like this will work naturally:

use ratatui_widgets::block::Block;
let block = Block::default();
let block_ref: &Block<'_> = block.as_ref();

This mostly improves ergonomics, but it can affect type inference in rare cases or conflict with downstream AsRef impls. If that happens, add an explicit type annotation or remove the redundant impl.

This is a breaking change.

Paragraph didn’t take into account alignment of the text it was created from:

let lines = vec![
Line::from("one"),
Line::from("double"),
Line::from("quadruple"),
];
let text = Text::from(lines).centered();
// used to be rendered left-aligned, now centered
let paragraph = Paragraph::new(text).block(block);

Now the Paragraph inherits the text alignment.

  • MSRV is now 1.88.0
  • Support constructing Line from &[Span] and Text from &[Line]
  • Support Modifier::HIDDEN in ratatui-crossterm for hidden text such as password fields
  • Support conversion between Crossterm styles (IntoCrossterm<ContentStyle>) and Style
  • Add impl From<u16> for Padding and Margin
  • Fix trailing-cell diffing when only cell style changes
  • Fix inline viewport resizing issues by clearing the screen
  • Fix the flex example colors for the macOS terminal
  • Avoid panic if Clear area is outside of buffer
  • Ensure consistent thumb size in Scrollbar while scrolling
  • Panic on conversion from Color::Reset to anstyle::Color with a more descriptive message

“Rats, we’re rats; we’re the rats.”