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;
// Span builders
let title = t.fg_accent.bold.build;
// Line compositor — no more vec![] + .build() on every span
let line = t.line.accent_bold.dim.success.build;
// Block builder — themed borders, title, focus state
let panel = t.block.focused.build;
// Status bar — key-value pairs
let status = t.status_line.kv.kv.build;
// Widget style bundles — Table, List, Tabs, Gauge, State
let ts = t.table_styles; // header, row, highlight, stripe
let ls = t.list_styles; // base, highlight, symbol
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;
Builders
Import ThemeExt to get all builders on any Theme:
Spans
t.fg_accent.bold.build // semantic color + modifiers
t.fg_success.italic.build // success, error, warning, info, dim, bright
t.fg_added.build // diff colors
t.badge.build // text on colored background
Line Compositor
// Compose multi-span lines without vec![] boilerplate
let line = t.line
.accent_bold
.dim
.success_bold
.dim
.error_bold
.build;
Block Builder
let panel = t.block.build; // themed borders + title
let active = t.block.focused.build; // accent-colored borders
let bare = t.block_plain.build; // no title
Status Line
let status = t.status_line
.kv
.kv
.kv_colored
.separator
.build;
Widget Style Bundles
// Table — header, row, highlight, zebra striping
let ts = t.table_styles;
let striped = zebra_rows;
// List — base, highlight, symbol
let ls = t.list_styles;
// Tabs — active, inactive
let tabs = t.tab_styles;
// Gauge — filled, base
let gs = t.gauge_styles;
// Input — text, placeholder, cursor, prompt, border, border_focused
let is = t.input_styles;
// Scrollbar — track, thumb
let sb = t.scrollbar_styles;
// Notifications — info, success, warning, error, body, background
let ns = t.notification_styles;
State-Aware Styles
// Resolve style based on widget state (focused, selected, disabled)
let ss = t.state_styles;
let style = ss.resolve;
Progress Bar & Separator
t.bar.width.build // ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱ 75%
t.separator_line // · · · · · · · · · · · ·
Full-Screen Canvas
// Paint themed background on the entire terminal — one line, all widgets inherit it.
// Plugins/widgets can still override bg on their own elements.
frame.render_widget;
Style Helpers
// For widget APIs that take Style (border_style, title_style)
t.style_accent t.style_border t.style_error
t.style_success t.style_warning t.style_info
t.style_bright t.style_dim t.style_surface
t.style_base // bg(background) + fg(text) — full-screen canvas
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, background |
Borders, focus highlight, app background |
| 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.
See all themes rendered: Theme Gallery
Custom Themes
Implement the Theme trait — 15 required methods, 12+ derived automatically.
Override background() to set a custom app background (defaults to Color::Reset):
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 27+ 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