v0.30.1

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: Shadows
Section titled “Block: Shadows”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)),);Canvas & Chart: Filled Area
Section titled “Canvas & Chart: Filled Area”Canvas and Chart widgets now support filled area rendering which helps with visualizing trends
and magnitudes in your data!
- In
Canvas, useFilledLine. - In
Chart, useGraphType::Areaand set the baseline viaDataset::fill_to_y(f64).
// In Canvasuse 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 Chartlet dataset = Dataset::default() .data(&data) .graph_type(GraphType::Area) .fill_to_y(0.0); // fill to y = 0
Chart::new(vec![dataset]);Buffer: Cell Diff Options
Section titled “Buffer: Cell Diff Options”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:
But the buffer may store the whole ANSI sequence in a single cell:
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.
Buffer: CellWidth trait
Section titled “Buffer: CellWidth trait”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(); // 3The main benefit is having one Ratatui-native API for terminal width calculation that works consistently across both strings and buffer cells.
no_std: Support Layout Cache
Section titled “no_std: Support Layout Cache”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"] }Performance: Fewer Allocations
Section titled “Performance: Fewer Allocations”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.
Examples: Volatility Surface
Section titled “Examples: Volatility Surface”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.
To try it out yourself:
git clone https://github.com/ratatui/ratatui && cd ratatui/
cargo run -p volatility-surfaceSymbols: Custom Markers
Section titled “Symbols: Custom Markers”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 symboluse 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: Multi-column Cells
Section titled “Table: Multi-column Cells”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:
Widgets: AsRef implementation
Section titled “Widgets: AsRef implementation”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: Inherit Alignment
Section titled “Paragraph: Inherit Alignment”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 centeredlet paragraph = Paragraph::new(text).block(block);Now the Paragraph inherits the text alignment.
Other 💼
Section titled “Other 💼”- MSRV is now 1.88.0
- Support constructing
Linefrom&[Span]andTextfrom&[Line] - Support
Modifier::HIDDENinratatui-crosstermfor hidden text such as password fields - Support conversion between Crossterm styles (
IntoCrossterm<ContentStyle>) andStyle - Add
impl From<u16>forPaddingandMargin - 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
Cleararea is outside of buffer - Ensure consistent thumb size in
Scrollbarwhile scrolling - Panic on conversion from
Color::Resettoanstyle::Colorwith a more descriptive message
“Rats, we’re rats; we’re the rats.”