ratatui-themekit
Semantic theme system for ratatui.
Stop hardcoding Color::Rgb(...) everywhere. Define what colors mean and let themes provide the actual values.
The Problem
// Without themekit — colors scattered everywhere, no consistency
let style = default.fg; // what is this?
let err = default.fg; // hope I remember
The Solution
use ;
let t = CatppuccinMocha;
// Tailwind-like builders — semantic, chainable, themed
let title = t.fg_accent.bold.build;
let ok = t.fg_success.bold.build;
let hint = t.fg_dim.build;
Quick Start
use Line;
use ;
let theme = resolve_theme;
let t = theme.as_ref;
// Build themed spans — never touch Style::default() again
let header = from;
// Widget styles (border_style, title_style)
let border = t.style_border;
let title = t.style_accent;
ThemeExt Builders (Tailwind-like API)
Import ThemeExt to get chainable builders on any Theme:
use ;
let t = CatppuccinMocha;
// Semantic span builders — color from theme slot
t.fg_accent // accent color
t.fg_dim // dimmed text
t.fg_bright // bright text
t.fg_success.bold // success + bold
t.fg_error.italic // error + italic
t.fg_warning // warning
t.fg_info // informational
t.fg_added // diff added
t.fg_removed // diff removed
t.fg_border // border/separator
// Modifiers chain
t.fg_accent.bold.italic.build
// Background color
t.fg_accent.on.build
// Badge (text on colored background)
t.badge.build
// Progress bar
t.bar.width.build // ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱ 75%
// Separator line
t.separator_line // · · · · · · · · · · · ·
// Style helpers for widget APIs (border_style, title_style)
t.style_accent // Style with accent fg
t.style_border // Style with border fg
t.style_error // Style with error fg
t.style_warning // Style with warning fg
Dynamic Colors
For colors not from a named theme slot (e.g., computed at runtime):
use ;
// Dynamic color span
let block_color = theme.block_file_edit;
with_color.bold.build
// Dynamic color style (for widgets)
style_fg
20 Semantic Color Slots
| Category | Slots | Purpose |
|---|---|---|
| Brand | accent, accent_dim |
Primary UI color, subtle highlights |
| Text | text, text_dim, text_bright |
Default, muted, emphasized text |
| Status | success, error, warning, info |
Semantic status indicators |
| Diff | diff_added, diff_removed, diff_context |
Code diff rendering |
| Structure | border, surface |
Panel borders, focused backgrounds |
| Derived | block_*, indicator_* |
Auto-derived from core slots |
11 Built-in Themes
| Theme | ID | Style |
|---|---|---|
| Catppuccin Mocha | catppuccin |
Warm dark, pastel accents (default) |
| Dracula | dracula |
Dark, vivid purples and greens |
| Nord | nord |
Arctic blue-gray, calm |
| Gruvbox Dark | gruvbox |
Retro warm, earthy tones |
| One Dark | one-dark |
Atom's classic blue |
| Solarized Dark | solarized |
Precision-engineered |
| Tailwind Dark | tailwind |
Tailwind CSS palette |
| Tokyo Night | tokyo-night |
Vivid blue accents |
| Rosé Pine | rose-pine |
Muted, elegant rose tones |
| Terminal Native | terminal |
Named ANSI colors only |
| No Color | no-color |
All Color::Reset for NO_COLOR |
Themes are pure data (ThemeData constants) — zero boilerplate, zero code duplication.
Custom Themes
Implement the Theme trait — 15 required methods, 10+ derived automatically:
use Color;
use Theme;
;
// All ThemeExt builders work automatically!
Serde Custom Themes
Load themes from config files with the serde feature:
use CustomTheme;
let toml = r#"
name = "My Theme"
id = "my-theme"
accent = { Rgb = [249, 115, 22] }
success = "Green"
error = "Red"
"#;
let theme: CustomTheme = from_str.unwrap;
NO_COLOR Support
Automatically respects the NO_COLOR standard:
use resolve_theme;
// When NO_COLOR is set, resolve_theme returns NoColor automatically
let theme = resolve_theme; // → NoColor if NO_COLOR is set
Runtime Theme Switching
use ;
// List available themes for a settings menu
for id in available_theme_ids
// Switch at runtime — zero code changes needed
let mut current = resolve_theme;
current = resolve_theme; // instant
Design Philosophy
- Semantic over literal — slots describe meaning, not appearance
- Builders over manual styling —
t.fg_accent("x").bold()notSpan::styled("x", Style::default().fg(...)) - Derived defaults — implement 15 methods, get 25+ color slots
- Zero opinion on layout — only colors, never widget structure
NO_COLORnative — accessibility built in, not bolted onSend + Sync— safe for async TUI architectures
License
MIT