Expand description
Garnish your Widgets
A powerful composition system for Ratatui widgets.
ratatui-garnish provides a flexible way to change the rendering of any Ratatui widget with
garnishes like borders, titles, padding, shadows, and styling. Garnishes can be layered
in any order, applied at runtime, and modified without altering the underlying widget. The
GarnishedWidget struct wraps a widget and a Vec of Garnish enums, maintaining zero-cost
abstractions and type safety without trait objects.
Want a margin outside a border? Garnish with Padding before a border. Need multiple borders or
titles? Simply add them! Writing custom widgets but want to avoid boilerplate for styling or
borders? Use ratatui-garnish with any widget implementing Widget or StatefulWidget.
§Example
use ratatui::{text::Text, style::{Color, Style}};
use ratatui_garnish::GarnishableWidget;
use ratatui_garnish::{border::RoundedBorder, title::{Title, Above}, Padding};
// Create a text widget with multiple decorations
let widget = Text::raw("Hello, World!\nTasty TUIs from Ratatui")
.garnish(RoundedBorder::default()) // Add a rounded border
.garnish(Title::<Above>::raw("My App")) // Add a title above
.garnish(Style::default().bg(Color::Blue)) // Set a background color
.garnish(Padding::uniform(1)); // Add padding inside
// Garnishes are applied in order during rendering§Getting Started
Import the GarnishableWidget trait to enable the garnish method on any Ratatui widget:
use ratatui_garnish::GarnishableWidget;This trait extends Widget. There is a similar traits GarnishableStatefulWidget for StatefulWidget.
The RenderModifier trait defines how garnishes modify rendering and layout. Ratatui’s
Style implements RenderModifier and Padding too (which is similair to the
Padding from Block but can be serialized) allowing their use as garnishes:
use ratatui::{style::{Color, Style}, text::Line};
use ratatui_garnish::{GarnishableWidget, RenderModifier, Padding};
let widget = Line::raw("Hello, World!")
.garnish(Style::default().bg(Color::Blue)) // Background for padded area
.garnish(Padding::horizontal(1)) // Padding on left and right
.garnish(Style::default().bg(Color::Red)) // Background for next padded area
.garnish(Padding::vertical(2)) // Padding on top and bottom
.garnish(Style::default().bg(Color::White)); // Background for the lineThe first call to garnish() returns a GarnishedWidget or GarnishedStatefulWidget, which wraps your
widget and a Vec of Garnish. It also has a garnish() method so you
can keep adding garnishes. At any time you can access the
garnishes you’ve added by treating GarnishedWidget like a Vec of
Garnish items, which is an enum that wraps all available garnishes.
let widget = Line::raw("Hello, World!")
.garnish(Style::default().bg(Color::Blue))
.garnish(Padding::horizontal(1));
assert!(widget[0].is_style()); // The first garnish we added
assert_eq!(widget.first_padding(), Some(&Padding::horizontal(1)));
// Let's look at all the garnishes
for garnish in &widget {
println!("{garnish:?}");
}Alternatively you can create a GarnishedWidget from a widget and a garnish using the new constructor
or from only a widget using from. Add garnishes with methods like push or extend.
use ratatui_garnish::{GarnishedWidget, RenderModifier, Padding};
use ratatui::{style::{Style, Color}, text::Line};
let mut widget = GarnishedWidget::from(Line::raw("Hello, World!"));
widget.push(Style::default().bg(Color::Green));§Available Garnishes
§Borders
- Standard:
PlainBorder,RoundedBorder,DoubleBorder,ThickBorder - Dashed variants:
DashedBorder,RoundedDashedBorder,ThickDashedBorder, - Custom:
CharBorder(single character, e.g.,****),CustomBorder(fully customizable character set) - Specialty:
QuadrantInsideBorder,QuadrantOutsideBorder,FatInsideBorder,FatOutsideBorder
§Titles
- Horizontal:
Title<Top>(over top border),Title<Bottom>(over bottom border),Title<Above>(reserves space above),Title<Below>(reserves space below) - Vertical:
Title<Left>(over left border),Title<Right>(over right border),Title<Before>(reserves space left),Title<After>(reserves space right)
§Shadows
Shadow(light░, medium▒, dark▓, or full█shades with full-character offsets)HalfShadow(full█or quadrant characters with half-character offsets)
§Padding
Padding(spacing around the widget), same asPaddingfromratatui::widgets::Block
§Built-in Ratatui Support
Style(background colors, text styling)
§Complex Compositions
Combine multiple garnishes for rich widget designs:
use ratatui_garnish::{
GarnishableWidget, RenderModifier,
title::{Title, Top, Bottom,},
border::DoubleBorder, Padding
};
use ratatui::{
text::Line,
style::{Color, Style, Modifier},
};
let complex_widget = Line::raw("Important Message")
// Add a margin
.garnish(Padding::uniform(2))
// Set Background color
.garnish(Style::default().bg(Color::DarkGray))
// Border with title
.garnish(Title::<Top>::styled("⚠ WARNING ⚠",
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)).margin(1))
.garnish(Title::<Bottom>::raw("Status: Active").right_aligned().margin(1))
.garnish(DoubleBorder::default())
.garnish(Padding::uniform(1));§Reusing Garnishes
Use the Garnishes vec and extend_from_slice or extend to apply
the same garnishes to multiple widgets:
let garnishes = garnishes![
Style::default().fg(Color::Blue),
DoubleBorder::default(),
Padding::uniform(2),
Style::default().fg(Color::White),
];
let mut widget = GarnishedWidget::from(Line::raw("First widget"));
widget.extend_from_slice(&garnishes);
let mut other_widget = Line::raw("Other widget")
.garnish(Title::<Top>::styled("Second",
Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)).margin(1));
other_widget.extend(garnishes);The GarnishableWidget and GarnishableStatefulWidget add the methods
garnishes and garnishes_from_slice to construct GarnishedWidgets directly
from Garnishes.
let widget = Line::raw("Widget")
.garnishes( garnishes![
Style::default().fg(Color::Blue),
DoubleBorder::default(),
Padding::uniform(2),
Style::default().fg(Color::White),
]);
// copy garnishes of widget to other_widget
let other_widget = Line::raw("Other Widget")
.garnishes_from_slice(widget.as_slice());§Features
§Serde support
Serialization & deserialization using serde can be enabled using the cargo feature
serde. When it is enabled all garnishes, the Garnish enum and the Garnishes
Vec can be serialized and deserialized. This makes it easy to add theme support
to your application.
§Decorated widget
The cargo feature decorated widget enables DecoratedWidget and DecoratedStatefulWidget
which wrap one widget with one garnish, like the traditional decorator pattern. It
offers little benefits over GarnishedWidget. It might be slightly faster
if you want to use only a small number of garnishes. The after_render functions
are rendered in reverse order.
use ratatui::{style::{Color, Style}, text::Text};
use ratatui_garnish::{
border::PlainBorder,
title::{Title, Top},
GarnishableWidget, Padding,
};
#[cfg(feature = "decorated_widget")]
let widget = Text::raw("Hello World!")
.decorate(Style::default().fg(Color::Red).bg(Color::White))
.decorate(Title::<Top>::raw("Paragraph").margin(1))
.decorate(PlainBorder::default())
.decorate(Padding::horizontal(2));§Compatibility
ratatui-garnish works seamlessly with any Ratatui widget implementing Widget or StatefulWidget,
following Ratatui’s conventions.
§Contributing
This is the first release of ratatui-garnish. More garnishes are planned, and contributions are
welcome!
Modules§
- border
- Border Garnishes
- shadow
- Shadow garnishes
- title
- A garnish that adds a styled text line to a widget at a specified position.
Macros§
Structs§
- Garnished
Stateful Widget - A widget that wraps another stateful widget with a vec of garnishes.
- Garnished
Widget - A widget that wraps another widget with a vector of garnishes.
- Garnishes
- A
VecofGarnishfor applying multiple garnishes to widgets. - Padding
- Padding garnish.
Enums§
- Garnish
- Enum wrapping all available garnishes.
Traits§
- Garnishable
Stateful Widget - A trait for stateful widgets that can be garnished.
- Garnishable
Widget - A trait for widgets that can be garnished.
- Render
Modifier - A trait that can modify the rendering of a widget.