ratatui-garnish
Garnish your Ratatui Widgets
ratatui-garnish is a powerful composition system for Ratatui widgets,
allowing you to change de rendering of any widget with garnishes like borders, titles, shadows,
padding, and styles in a flexible, type-safe way. Layer multiple
garnishes in any order, apply them at runtime, and modify them
without altering the underlying widget. With zero-cost abstractions
and no trait objects, ratatui-garnish ensures performance
and type safety.
Want a margin outside a border? Add Padding before a border. Need
multiple titles or a shadow effect? Just garnish! Writing custom
widgets but dreading the boilerplate for styling or borders?
Use ratatui-garnish with any widget implementing
Widget or StatefulWidget.
Features
- Composable Modifications: Layer borders, titles, shadows, padding, and styles in any order.
- Type-Safe: No trait objects or dynamic dispatch, preserving Rust's type system.
- Zero-Cost Abstractions: Efficient rendering with no overhead for unused garnishes.
- Flexible: Apply or modify garnishes at runtime.
- Seamless Integration: Works with any Ratatui widget.
Installation
Add ratatui-garnish to your Cargo.toml:
[]
= "0.1.0"
= "0.29"
Usage
Import the GarnishableWidget trait to add the garnish method to
any Ratatui widget:
use ;
use ;
// Create a text widget with multiple decorations
let widget = raw
.garnish // Add a rounded border
.garnish // Add a title above
.garnish // Set a background color
.garnish; // Add padding inside
Garnishes are applied in the order they are added, with before_render
effects (e.g., styles, shadows) applied first, followed by the widget,
and then after_render effects (e.g., titles over borders).
Complex Example
Combine multiple garnishes for a polished UI:
use ;
use ;
let widget = raw
.garnish // Margin outside
.garnish // Background color
.garnish
.garnish
.garnish; // Double border
Reusing Garnishes
Use the Garnishes vector to apply the same garnishes to multiple widgets:
use ;
use ;
let garnishes = garnishes!;
let mut widget1 = raw
.garnish;
widget1.extend_from_slice;
let mut widget2 = raw
.garnish;
widget2.extend;
Or construct GarnishedWidget directly from a widget with
Garnishes using the GarnishableWidget trait:
let widget = raw
.garnishes;
// copy garnishes of widget to other_widget
let other_widget = raw
.garnishes_from_slice;
Accessing Garnishes
Treat a GarnishedWidget like a Vec to inspect or modify its garnishes:
let widget = raw
.garnish
.garnish;
assert!;
assert_eq!;
Available Garnishes
Borders
- Standard:
PlainBorder,RoundedBorder,DoubleBorder,ThickBorder - Dashed Variants:
DashedBorder,RoundedDashedBorder,ThickDashedBorder, - Custom:
CharBorder(single character, e.g.,****),CustomBorder(custom 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 offsetsHalfShadow: Full (█) or quadrant characters (e.g.,▗) with half-character offsets
Padding
Padding: Spacing around the widget.
Built-in Ratatui Support
Style: Background colors, text styling
Recipes
Here are some examples with screenshots of what you can do with ratatui-garnish. I only show the garnishes used, the complete code can be found in the examples directory.
Padding
This example shows a combination of Style and Padding
garnishes on a ratatui::text::Line widget.
garnishes!
Borders
You can add any combination of borders to a widget, in this
example it is again a ratatui::text::Line.
garnishes!;
Titles
This example shows the title garnishes, notice the difference between titles that reserve space (the triangles) and those that render over the border.
garnishes!;
Shadow
Here we add a Title::<Above> and a HalfShadow to a
ratatui::widgets::Paragraph widget.
garnishes!;
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.
Traditional decorator
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. I use it to compare GarnishedWidget to,
e.g. in benchmarks.
Performance
- Garnishes are applied in order, allowing precise control over rendering.
- Area modifications are accumulated efficiently.
- Zero-cost abstractions ensure no runtime overhead for unused garnishes.
- No dynamic dispatch or type erasure, preserving type safety and performance.
The benches directory contains two benchmarks using criterion.
compare_compositions compares 3 ways of decorating widgets:
the Ratatui way, traditional decorator and flat decorator.
The other one traditional_and_flat_decorator compares the two decorators
with different numbers of garnishes. Both need the decorator_widget
feature (for the traditional decorator pattern).
Flat decorator design pattern
Ratatui-garnish uses a flat decorator. I wrote an article about this pattern and the design of ratatui-garnish: Garnish your widgets: flexible, dynamic and type-safe composition in Rust
Compatibility
ratatui-garnish integrates seamlessly with Ratatui widgets implementing
Widget or StatefulWidget following Ratatui's conventions.
Contributing
This is the first release of ratatui-garnish, and more garnishes are
planned! Contributions are welcome.
License
Licensed under the MIT License. See the LICENSE file for details.
Acknowledgements
Built with love for the Rust and Ratatui communities. Inspired by the need for flexible, reusable widget modification.