Skip to content

v0.28.0

https://github.com/ratatui/ratatui/releases/tag/v0.28.0

⚠️ See the breaking changes for this release.

Crossterm is updated to version 0.28.0, which is a semver incompatible version with the previous version (0.27.0). Ratatui re-exports the version of crossterm that it is compatible with under ratatui::crossterm, which can be used to avoid incompatible versions in your dependency list.

See this issue for more information.


We have introduced a new variant to GraphType, named Bar, which is designed to draw a bar for each point in the dataset:

let chart = Chart::new(vec![Dataset::default()
.data(&data)
.marker(symbols::Marker::Dot)
.graph_type(GraphType::Bar)]);

Demo


We added a new example which demonstrates how to use Ratatui with widgets that fetch data asynchronously.

352719354-4c70e77d-b24d-4ccf-8af6-1fe1829fb863

The code is available here.


Previously, Axis::labels accepted a Vec<Span>. To make it more flexible, we have changed it to accept a vector of any type that can be converted into a [Line] (e.g., &str, String, &Line, Span, etc.). This means any code using conversion methods that infer the type will need to be rewritten as follows.

Axis::default().labels(vec!["a".into(), "b".into()])
Axis::default().labels(["a", "b"])

We have added a new method to Terminal called try_draw, which functions similarly to Terminal::draw but allows the render callback to be a function or closure that returns a Result instead of nothing (()).

This makes it easier to handle fallible rendering methods using the ? operator:

terminal.try_draw(|frame| {
some_method_that_can_fail()?;
another_fallible_method()?;
Ok(())
})?;

The method returns Result::Ok with a CompletedFrame if successful, or Result::Err with the std::io::Error that caused the failure.


Terminal: Make terminal module private 🔒

Section titled “Terminal: Make terminal module private 🔒”

The terminal module is now private and can not be used directly. The types under this module are exported from the root of the crate.

use ratatui::terminal::{CompletedFrame, Frame, Terminal, TerminalOptions, ViewPort};
use ratatui::{CompletedFrame, Frame, Terminal, TerminalOptions, ViewPort};

This simplifies the public API, making it more user-friendly for those unfamiliar with Rust’s re-exports and avoiding clashes with other modules named terminal in backend code.


Backend: Add get/set_cursor_position() 📍

Section titled “Backend: Add get/set_cursor_position() 📍”

If you implement the Backend trait yourself, you need to update the implementation and add the get/set_cursor_position methods, which indicates more clearly what about the cursor to set.

These new methods return/accept Into<Position> which can be either a Position or a (u16, u16) tuple.

backend.set_cursor_position(Position { x: 0, y: 20 })?;
let position = backend.get_cursor_position()?;
terminal.set_cursor_position((0, 20))?;
let position = terminal.set_cursor_position()?;

You can remove the get/set_cursor methods from your implementation as they are deprecated and a default implementation for them exists.


Buffer used to access elements with buf.get(x, y) or buf.get_mut(x, y). Now, we have added support for index operators and introduced buf.cell() and buf.cell_mut() methods.

These new methods use Into<Position> for coordinates, making them easier to use and safer by returning Option<&Cell> and Option<&mut Cell>, which helps avoid panics (yay).

let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 10));
// Access cells
let cell = buf[(0, 0)];
let cell = buf[Position::new(0, 0)];
// Get symbol
let symbol = buf.cell((0, 0)).map(|cell| cell.symbol());
let symbol = buf.cell(Position::new(0, 0)).map(|cell| cell.symbol());
// Set symbol
buf[(0, 0)].set_symbol("🐀");
buf[Position::new(0, 0)].set_symbol("🐀");
buf.cell_mut((0, 0)).map(|cell| cell.set_symbol("🐀"));
buf.cell_mut(Position::new(0, 0)).map(|cell| cell.set_symbol("🐀"));

The existing get() and get_mut() methods are now marked as deprecated.


It is just the more correct name. 🧀

Frame::size is now deprecated.


Text: Add Add and AddAssign implementations ✏️

Section titled “Text: Add Add and AddAssign implementations ✏️”

You can now combine Line, Span, and Text types together while inferring their types!

let line = Span::raw("Red").red() + Span::raw("blue").blue();
let line = Line::raw("Red").red() + Span::raw("blue").blue();
let line = Line::raw("Red").red() + Line::raw("Blue").blue();
let text = Line::raw("Red").red() + Line::raw("Blue").blue();
let text = Text::raw("Red").red() + Line::raw("Blue").blue();
let mut line = Line::raw("Red").red();
line += Span::raw("Blue").blue();
let mut text = Text::raw("Red").red();
text += Line::raw("Blue").blue();
line.extend(vec![Span::raw("1"), Span::raw("2"), Span::raw("3")]);

The ToText trait no longer has a lifetime parameter.

This change simplifies the trait and makes it easier to implement.


We have implemented new scroll_down_by(u16) and scroll_up_by(u16) methods for both ListState and TableState, which allow you to scroll through the items by a specified number of positions.

let mut state = ListState::default();
state.select(Some(2));
state.scroll_down_by(4);
assert_eq!(state.selected, Some(6));
let mut state = TableState::default();
state.select(Some(3));
state.scroll_up_by(3);
assert_eq!(state.selected, Some(0));

You can now navigate in the Table widget by using the following methods!

let mut state = TableState::default();
state.select_first();
state.select_next();
state.select_previous();
state.select_last();

This is the equivalent API as in ListState.


We started using Bencher.dev to track benchmarks over time and easily catch any regressions.

You can view our benchmarks at https://bencher.dev/console/projects/ratatui.

For discussions about future improvements, check out the tracking issue.


We have started experimenting with cargo-semver-checks in our CI to lint our API for semver violations!

See the PR for related discussion.


  • Return Size from Backend::size instead of Rect (#1254)
  • Remove unnecessary synchronization in Layout cache (#1245)
    • Layout::init_cache no longer returns bool and takes a NonZeroUsize instead of usize
  • Remove unnecessary allocations when creating Lines (#1237)
  • Avoid extra allocations when rendering List & Table(#1244 & #1242)
  • Add Size::ZERO and Position::ORIGIN constants to Layout (#1253)
  • Enable serde for Margin, Position, Rect and Size (#1255)
  • Prevent area mismatch in TestBackend (changes the serde representation) (#1252)
  • Allow removing all the axis labels in Chart (#1282)
  • Only apply style to first line when rendering a Line (#1247)
  • Ensure emojis are rendered (#1258)
  • Add Code of Conduct (#1279)

“If you are what you eat, then I only want to eat the good stuff.” – Remy