Skip to content


⚠️ See the breaking changes for this release.

Ratatui Website πŸ“š

We revamped our entire website ( with Astro and updated the documentation/tutorials.


Here are some of the sections that we recommend checking out:

All the code is available at

Check out the good first issues here if you are interested in contributing to our website!

Awesome Ratatui πŸ¦€

We created an awesome list for curating the awesome applications/libraries built with ratatui!


If you have something built with/for Ratatui, feel free to add it to this list!

Ratatui Templates πŸ“

We gathered the available ratatui templates under a single repository:

Currently, we have two templates:

  • Simple: A template for bootstrapping a simple ratatui application quickly.
  • Async: A more advanced template which uses crates such as tokio and tracing for creating an async ratatui application.

They can be used with cargo-generate as follows:

Terminal window
cargo generate ratatui-org/ratatui-template

This repository is still under construction so feel free to take a look and help us out. Additionally, we really appreciate if you have another template and interested in contributing it.

New/Updated Examples πŸ†•

We added a new example (ratatui-logo) which shows how to render a big text using half blocks.


We also updated the colors_rgb example to add animation and a FPS counter.

List: Better Construction ✨

List::new now accepts IntoIterator<Item = Into<ListItem>> which means you can build a List like following:

List::new(["Item 1", "Item 2"])

However, this change will throw a compilation error for IntoIterators with an indeterminate item (e.g. empty vectors):

let list = List::new(vec![]);
// instead, this should be:
let list = List::default();

List: Line Alignment πŸ“

The List widget now respects the alignment of Lines and renders them as expected.

For example:

let items = [

Will result in a list:


List: start_corner -> direction

The previous name start_corner did not communicate clearly the intent of the method.

A new method List::direction and a new enum ListDirection were added.

List::start_corner is now deprecated.

Layout: Better Construction ✨

We added a convenience function to create a layout with a direction and a list of constraints which are the most common parameters that would be generally configured using the builder pattern.

let layout = Layout::new(Direction::Horizontal, [

The constraints can be passed in as any iterator of constraints.

Layout: Allow Configuring Fill πŸ–ŒοΈ

The layout split will generally fill the remaining area when split() is called.

With this feature, we now allow the caller to configure how any extra space is allocated to the Rects. This is useful for cases where the caller wants to have a fixed size for one of the Rects, and have the other Rects fill the remaining space.

To use this feature, add unstable or unstable-segment-size feature flag to Cargo.toml. The reason for this is that the exact name for the types is still being bikeshedded.

The default behavior is SegmentSize::LastTakesRemainder, which gives the last segment the remaining space.

[Constraint::Percentage(50), Constraint::Percentage(25)],


SegmentSize::None will disable this behavior.

[Constraint::Percentage(50), Constraint::Percentage(25)],


To configure the layout to fill the remaining space evenly, use Layout::segment_size(SegmentSize::EvenDistribution).

See the documentation for Layout::segment_size() and layout::SegmentSize for more information.

Table: Segment Size πŸ—ƒοΈ

Similarly to the previous entry, this works just like Layout::segment_size but for tables:

let widths = [Constraint::Min(10), Constraint::Min(10), Constraint::Min(10)];
let table = Table::new([])

It controls how to distribute extra space to an underconstrained table. The default, legacy behavior is to leave the extra space unused.

The new options are LastTakesRemainder which gets all space to the rightmost column that can used it, and EvenDistribution which divides it amongst all columns.

Table: Width Improvements ✨

Table::new() now requires specifying the widths of the columns

Previously, Tables could be constructed without widths. In almost all cases this is an error so we made the widths parameter mandatory.

Which means you need to update your existing code to:

Table::new(rows, widths)

For ease of automated replacement, in cases where the amount of code broken by this change is large or complex, it may be convenient to replace Table::new with Table::default().rows:


Table::widths() now accepts AsRef<[Constraint]>

This allows passing an array, slice or Vec of constraints, which is more ergonomic than requiring this to always be a slice.

Table::default().widths([Constraint::Length(5), Constraint::Length(5)]);
Table::default().widths(&[Constraint::Length(5), Constraint::Length(5)]);
// widths could also be computed at runtime
let widths = vec![Constraint::Length(5), Constraint::Length(5)];

Constraint Helpers πŸ†˜

We added helper methods that convert from iterators of u16 values to the specific Constraint type.

This makes it easy to create constraints like:

// a fixed layout
let constraints = Constraint::from_lengths([10, 20, 10]);
// a centered layout
let constraints = Constraint::from_ratios([(1, 4), (1, 2), (1, 4)]);
let constraints = Constraint::from_percentages([25, 50, 25]);
// a centered layout with a minimum size
let constraints = Constraint::from_mins([0, 100, 0]);
// a sidebar / main layout with maximum sizes
let constraints = Constraint::from_maxes([30, 200]);

Paragraph: line_count & line_width πŸ“

We now have a new unstable feature which helps getting information about a rendered paragraph

Enable the unstable or unstable-rendered-line-info feature flag to get access to the following methods:

  • Paragraph::line_count: Takes a bounding width and returns the number of lines necessary to display the full paragraph when rendered. The motivation for this method is to use it with scrollbars. If a paragraph has wrapped text, it is currently impossible to get the number of lines it will need.
  • Paragraph::line_width: Returns the smallest line width needed to fully display every line of a paragraph. This is simply the maximum line widths.

Together, these two methods can be used to calculate the minimum bounding box for a paragraph. Please note that they are experimental and may change in the future.

Rect: offset πŸ“

Rect has a new method called offset and it can be used to create a new Rect that is moved by the amount specified in the x and y direction. These values can be positive or negative. This is useful for manual layout tasks.

let rect = area.offset(Offset { x: 10, y -10 });

IntoIterator for Constraints πŸ”„

Layout and Table now accept IntoIterator for constraints with an Item that is AsRef<Constraint>.

This allows pretty much any collection of constraints to be passed to the layout functions including arrays, vectors, slices, and iterators (without having to call collect() on them).

let layout = Layout::default().constraints([Constraint::Min(0)]);
let layout = Layout::default().constraints(&[Constraint::Min(0)]);
let layout = Layout::default().constraints(vec![Constraint::Min(0)]);
let layout = Layout::default().constraints([Constraint::Min(0)].iter().filter(|_| true));
let layout = Layout::default().constraints([1,2,3].iter().map(|&c| Constraint::Length(c)));

Chart: Legend Position πŸ“Š

Chart has a new setter method called legend_position:

let chart: Chart = Chart::new(vec![])

See Chart::LegendPosition for other possible values.

Default Highlight Style 🎨

Previously the default highlight_style was set to Style::default(), which meant that the highlight style was the same as the normal style.

This means that for example a Tabs widget in the default configuration would not show any indication of the selected tab.

Now we set the highlight_style to the reversed style (Style::new().reversed()) as default.

Tab: Custom Padding πŸ“‡

The Tab widget now contains padding_left and padding_right properties.

Those values can be set with functions padding_left(), padding_right(), and padding() which all accept Into<Line>.

For convenience, you can also use the padding method to set left/right padding accordingly:

// A space on either side of the tabs.
let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding(" ", " ");
/// Nothing on either side of the tabs.
let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).padding("", "");

Deprecate Cell::symbol 🚫

The Cell::symbol field is now accessible via a getter method (symbol()). This will allow us to make future changes to the Cell internals such as replacing String with compact_str.


Remove Deprecated Items πŸ’€

We removed Axis::title_style and Buffer::set_background which were deprecated since 0.10.

Other πŸ’Ό

  • Add new setter methods: Span::content, Span::style
  • Implement From trait for crossterm/termion to Style related structs
  • Rewrite the insert_before method on Terminal to fix a bug and remove the height cap
  • Add WrappedLine struct to reflow module
  • Add documentation to Sparkline, Chart, ListItem and many other parts of the codebase
  • Reorganize the modules/documentation to respect the natural reading order
  • Remove unnecessary dynamic dispatch and heap allocation from widgets
  • Add Vim key bindings (movement) to the examples
  • Add #[must_use] to fluent setter methods
  • Create a security policy
  • Bump MSRV to 1.70.0

And lastly, we welcome @Valentin271 on board as a maintainer! πŸ₯³

New Socials 🌐

We created the following social media presences for ratatui:

Give us a follow for the latest ratatui news & other cool stuff!

GitHub Sponsors πŸ’–

We have enabled GitHub Sponsors for our organization:

Our mission is to empower developers to create more interactive and visually rich command-line terminal user interfaces (TUIs). By sponsoring Ratatui, you’re not just funding a project; you’re helping make the ecosystem of terminal applications in Rust better.