Skip to content
381 changes: 381 additions & 0 deletions src/content/docs/highlights/v0.30.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
---
title: v0.30.1
---

![animation](../../../assets/ratatui-animation.gif)

We are excited to announce Ratatui 0.30.1! 🐁🚀🌕

This release adds [Block shadows](#block-shadows), filled area rendering for
[Canvas and Chart](#canvas--chart-filled-area), more precise
[buffer diff options](#buffer-cell-diff-options), and multi-column
[Table cells](#table-multi-column-cells). It also includes new widget ergonomics such as
[`Fill`](#widgets-fill), [`CellWidth`](#buffer-cellwidth-trait), custom
[markers](#symbols-custom-markers), new examples and more!

See the [changelog](https://github.com/ratatui/ratatui/blob/main/CHANGELOG.md) for the full list of
changes. See the breaking changes for this release
[here](https://github.com/ratatui/ratatui/blob/main/BREAKING-CHANGES.md).

## 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.

```rust
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](https://github.com/user-attachments/assets/103ddc17-6536-424c-a7a8-8895540dd145)

## 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`, use `FilledLine`.
- In `Chart`, use `GraphType::Area` and set the baseline via `Dataset::fill_to_y(f64)`.

```rust
// 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]);
```

<img width="1460" height="879" alt="изображение"
src="https://github.com/user-attachments/assets/c7b534b1-2afb-49c7-9c56-178e6ba9e844"
/>

<img width="1460" height="881" alt="изображение"
src="https://github.com/user-attachments/assets/53592e2c-ee89-4481-9099-be06480d305a"
/>

## 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:

```svgbob
0 1 2
┌─────┬─────┬─────┐
0 │ r │ e │ d │
└─────┴─────┴─────┘
```

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

```svgbob
0 1 2
┌─────────────────────┬─────┬─────┐
0 │ "\x1b[31mred\x1b[0m │ │ │
└─────────────────────┴─────┴─────┘
```

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.

:::note

One example use case is [tui-link](https://github.com/benjajaja/tui-link), which uses
`CellDiffOption::ForcedWidth` to render OSC 8 hyperlinks while keeping diffing aligned with the
visible text width.

:::

:::caution[Deprecation notice]

This release keeps `Cell::skip` (bool) as a deprecated field for patch-release compatibility, but
`CellDiffOption` is the new API going forward.

When both are present, `CellDiffOption` takes precedence.

:::

## 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](#buffer-cell-diff-options) above).

```rust
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.

:::note

Internally, `CellWidth` uses the same Unicode display-width logic as the
[unicode-width](https://docs.rs/unicode-width/) crate. For `&str`, it computes width from the string
content; for `Cell`, it does the same unless `CellDiffOption::ForcedWidth` is set, in which case the
forced width takes precedence.

:::

:::note

`CellWidth` also includes a terminal-compatibility fix for halfwidth katakana dakuten and handakuten
(`U+FF9E` / `U+FF9F`). While `unicode-width` reports those code points as zero-width, terminals
usually render them as occupying one cell, so strings like `ガ` and `パ` now report the correct
width.

:::

## Terminal: Apply Buffer

Ratatui now exposes a public API for applying and flushing the terminal buffer. This lets apps
commit incremental writes to the terminal buffer without putting all rendering inside a single
`Terminal::draw` closure.

```rust
terminal.current_buffer_mut().merge(&custom_buffer);
terminal.apply_buffer()?;
```

This is especially useful for ECS-style apps, such as
[`bevy_ratatui`](https://github.com/ratatui/bevy_ratatui), where independent systems may render into
their own buffer before applying it to the terminal at the end of a frame.

## 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:

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

:::note

See [this issue](https://github.com/ratatui/ratatui/issues/2419) about potentially removing the
`layout-cache` feature and always enabling it. Please chime in if you have opinions on this or have
tested the performance implications in your app!

:::

## 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.

:::note[Why (who cares?)]

it's the mice. on embedded devices, allocating a short-lived, contiguous block of up to 40-50kb is
problematic due to heap fragmentation in combination with tiny heaps. the requirements for a full
refresh over 1200 terminal cells is 37.5kb+vec growth padding, but since we're dealing with a
contiguous block of memory, the actual memory requirements are considerably higher, depending on
user-land allocation patterns.

:::

## 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.

![volatility-surface](https://github.com/user-attachments/assets/9613ef26-c766-4f6b-80eb-5bfaa2a7963d)

To try it out yourself:

```sh
git clone https://github.com/ratatui/ratatui && cd ratatui/

cargo run -p volatility-surface
```

![](https://github.com/user-attachments/assets/68698cb0-c5d5-4b41-a3c3-65ec8fff12f5)

## 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:

```rust
use ratatui::{symbols::Marker, widgets::canvas::Canvas};

let canvas = Canvas::default()
.marker(Marker::Custom('+')); // Use '+' as the marker symbol
```

```rust
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: 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.

```rust
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:

```svgbob
0 1 2
┌───────────┬─────┐
0 │ Name │Score│
├─────┬─────┼─────┤
1 │Alice│ A. │ 42 │
└─────┴─────┴─────┘
```

## Widgets: `Fill`

Ratatui now includes a `Fill` widget for painting every cell in an area with the same symbol and
style.

```rust
use ratatui::style::Stylize;
use ratatui::widgets::{Fill, Widget};

Fill::new("X").blue().bold().render(area, buf);
```

This is useful when you want to quickly fill a region with a repeated pattern, background, or
placeholder content without manually iterating over the buffer.

## Widgets: `AsRef` implementation

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

This means code like this will work naturally:

```rust
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](https://github.com/ratatui/ratatui/blob/main/BREAKING-CHANGES.md#adding-asref-impls-for-widgets-may-affect-type-inference-2297).

## Paragraph: Inherit Alignment

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

```rs
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.

## Other 💼

- 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."_

– [Rat Movie](https://www.youtube.com/watch?v=OXQwx1EolD8)
Loading