Themes
Themes control the visual appearance of the TUI, including colors for text, borders, messages, code blocks, and interactive elements. Agent Air includes two built-in themes and supports creating custom themes for brand consistency or personal preference.
A well-designed theme improves readability and reduces eye strain during extended use. The theming system applies consistent styling across all widgets, ensuring visual coherence throughout the interface. Whether you use the built-in themes or create your own, the same properties control appearance everywhere.
Built-in Themes
Agent Air provides two built-in themes optimized for different terminal backgrounds. These themes have been carefully designed to provide good contrast and readability while looking pleasant across a variety of terminal emulators.
Both themes include complete styling for all UI elements. Switching between them demonstrates the full scope of what themes control, from subtle details like timestamp colors to prominent elements like message role prefixes.
| Name | Display Name | Description |
|---|---|---|
dark | Dark | Optimized for dark terminal backgrounds |
light | Light | Optimized for light terminal backgrounds |
The dark theme is the default. Both themes provide complete styling for all UI elements including chat messages, markdown rendering, tool execution, panels, and popups.
Setting the Theme
Set the initial theme when configuring the TUI using the with_theme method. The theme name must match one of the registered themes—the built-in themes are always available.
Choosing a theme at startup ensures the interface looks correct from the first frame. Users can switch themes later, but starting with the right theme avoids a flash of incorrect colors.
use agent_air::AgentAir;
use agent_air::tui::TuiRunner;
AgentAir::with_config(&config)?
.into_tui()
.with_theme("dark")
.run()?;
You can also set the theme programmatically at startup using the initialization functions:
use agent_air::tui::themes::{get_theme, init_theme};
if let Some(theme) = get_theme("light") {
init_theme("light", theme);
}
Runtime Theme Switching
Themes can be changed while the application is running, allowing users to adjust the appearance based on their environment or preference. The change takes effect on the next render cycle, providing immediate visual feedback.
Runtime switching is useful when users move between different lighting conditions or simply want to try different looks without restarting the application.
use agent_air::tui::themes::{get_theme, set_theme};
if let Some(theme) = get_theme("light") {
set_theme("light", theme);
}
Users can also switch themes interactively using the theme picker widget, which is available via the /theme command in the default configuration.
Getting the Current Theme
Access the current theme for use in custom widgets or other code that needs to style content consistently. The theme provides all the style information needed to render themed content.
Custom widgets should always use theme properties rather than hardcoded colors. This ensures they remain visually consistent when users switch themes.
use agent_air::tui::themes::{theme, current_theme_name};
// Get the current theme
let current = theme();
// Use current.text, current.border, etc.
// Get the theme name
let name = current_theme_name();
println!("Current theme: {}", name);
The theme() function returns a clone of the current theme. If no theme has been set, it returns the default theme.
Theme Lookup Functions
The theme registry provides functions for accessing themes programmatically. These functions let you list available themes, look up specific themes by name, and access theme metadata.
Use these functions when building theme-related UI, such as a custom theme picker, or when you need to access themes before the TUI has fully initialized.
use agent_air::tui::themes::themes::{get_theme, list_themes, THEMES};
// Get a specific theme by name
if let Some(theme) = get_theme("dark") {
// Use the theme
}
// List all available theme names
let names: Vec<&'static str> = list_themes();
// Access theme metadata
for info in THEMES {
println!("{} ({}) - {}",
info.name,
info.display_name,
if info.is_dark { "dark" } else { "light" }
);
}
Theme Structure
The Theme struct contains approximately 50 style properties organized by category. Each property is a Style object (or Modifier for text formatting) that can specify foreground color, background color, and text modifiers.
Understanding the theme structure helps when creating custom themes or debugging styling issues. Properties are grouped logically by the UI elements they affect.
Base Styles
Base styles provide the foundation for the entire interface. The background style fills the screen, while the text style is the default for any content without a more specific style.
| Property | Purpose |
|---|---|
background | Background color for the entire UI |
text | Default text color |
Border Styles
Border styles define the visual boundaries between UI elements. Focused borders indicate which element has keyboard focus, helping users understand where their input will go.
| Property | Purpose |
|---|---|
border | Default border color |
border_focused | Border color for focused elements |
Message Role Styles
Message role styles distinguish between different participants in the conversation. Clear visual differentiation helps users quickly scan conversations and identify who said what.
| Property | Purpose |
|---|---|
user_prefix | Prefix for user messages |
system_prefix | Prefix for system messages |
assistant_prefix | Prefix for assistant messages |
timestamp | Message timestamps |
Markdown Styles
Markdown styles control how formatted text appears in assistant responses. Good markdown styling improves readability of structured content like code blocks, lists, and headings.
| Property | Type | Purpose |
|---|---|---|
bold | Modifier | Bold text |
italic | Modifier | Italic text |
strikethrough | Modifier | Strikethrough text |
inline_code | Style | Inline code snippets |
link_text | Style | Link display text |
link_url | Style | Link URL |
heading_1 through heading_4 | Style | Heading levels |
code_block | Style | Fenced code block content |
Tool Execution Styles
Tool execution styles provide visual feedback about tool state. Different colors for executing, completed, and failed states help users quickly understand what happened without reading detailed output.
| Property | Purpose |
|---|---|
tool_header | Tool name and header |
tool_executing | Tool in progress indicator |
tool_completed | Tool completed successfully |
tool_failed | Tool execution failed |
UI Panel Styles
Panel styles apply to interactive elements like permission panels, question panels, and buttons. These styles need to work together to create a cohesive interface for user interactions.
| Property | Purpose |
|---|---|
help_text | Help and instruction text |
muted_text | De-emphasized text |
focused_text | Currently focused text |
selected | Selected option |
unselected | Unselected option |
button_confirm | Confirm button |
button_cancel | Cancel button |
warning | Warning messages |
Creating Custom Themes
Create custom themes using the define_theme! macro or by manually constructing a Theme struct. Custom themes let you match your organization’s brand colors, optimize for specific terminal environments, or simply express personal preference.
The macro approach is quickest when you have a color palette in mind, while manual construction gives complete control over every style property.
Using the define_theme! Macro
The macro generates a complete theme from a 13-color palette. This approach ensures all theme properties are set consistently based on your chosen colors, avoiding the tedium of configuring 50+ properties individually.
Each color parameter has a semantic meaning that guides how it’s applied throughout the theme. Understanding these meanings helps you choose colors that work well together.
use agent_air::define_theme;
use agent_air::tui::themes::theme::Theme;
define_theme! {
bg: (0x1a, 0x1b, 0x26), // Primary background
bg_alt: (0x16, 0x16, 0x1e), // Secondary background
fg: (0xc0, 0xca, 0xf5), // Primary text
fg_alt: (0x56, 0x5f, 0x89), // Muted text
base4: (0x3b, 0x40, 0x61), // Borders
red: (0xf7, 0x76, 0x8e), // Errors
orange: (0xff, 0x9e, 0x64), // Warnings
yellow: (0xe0, 0xaf, 0x68), // Highlights
green: (0x9e, 0xce, 0x6a), // Success
cyan: (0x7d, 0xcf, 0xff), // Links
blue: (0x7a, 0xa2, 0xf7), // User messages
magenta: (0xbb, 0x9a, 0xf7), // Assistant messages
violet: (0x9d, 0x7c, 0xd8) // Special elements
}
The macro maps these colors to all 50+ theme properties automatically.
Color Parameter Mapping
This table shows how each macro parameter maps to theme elements. Use this as a guide when choosing colors to ensure they work well for their intended purposes.
| Parameter | Maps To |
|---|---|
bg | Main background |
bg_alt | Code blocks, selected item backgrounds |
fg | Primary text, headings |
fg_alt | Muted text, timestamps, help text |
base4 | Borders, separators |
red | Errors, failed states, cancel buttons |
orange | Tool headers, warnings |
yellow | System messages, inline code, focus indicators |
green | Success states, connected indicator, confirm buttons |
cyan | Focused elements, links, selected items |
blue | User messages |
magenta | Assistant messages, categories |
violet | Link URLs, special elements |
Manual Theme Construction
For complete control, construct the Theme struct directly. This approach is necessary when you need styles that the macro can’t express, such as different modifiers on certain elements or background colors that don’t fit the standard pattern.
Start with the default theme and modify specific properties to reduce boilerplate while ensuring all properties have valid values.
use ratatui::style::{Color, Modifier, Style};
use agent_air::tui::themes::theme::Theme;
fn custom_theme() -> Theme {
Theme {
background: Style::default().bg(Color::Rgb(26, 27, 38)),
text: Style::default().fg(Color::Rgb(192, 202, 245)),
border: Style::default().fg(Color::Rgb(59, 64, 97)),
border_focused: Style::default().fg(Color::Rgb(125, 207, 255)),
user_prefix: Style::default().fg(Color::Rgb(122, 162, 247)),
assistant_prefix: Style::default().fg(Color::Rgb(187, 154, 247)),
system_prefix: Style::default().fg(Color::Rgb(224, 175, 104)),
timestamp: Style::default().fg(Color::Rgb(86, 95, 137)),
bold: Modifier::BOLD,
italic: Modifier::ITALIC,
inline_code: Style::default().fg(Color::Rgb(224, 175, 104)),
heading_1: Style::default()
.fg(Color::Rgb(125, 207, 255))
.add_modifier(Modifier::BOLD),
// ... remaining properties
..Default::default()
}
}
Registering Custom Themes
Use a custom theme by initializing it at startup. The theme becomes the active theme immediately and remains available for the duration of the application.
Register your theme early in the application lifecycle, before any rendering occurs. This ensures the theme is available when the TUI initializes.
use agent_air::tui::themes::init_theme;
fn main() {
let my_theme = custom_theme();
init_theme("my-custom-theme", my_theme);
// Start agent...
}
Extending Built-in Themes
Modify an existing theme by cloning it and changing specific properties. This approach lets you make targeted adjustments without recreating the entire theme from scratch.
Extending built-in themes is useful when you like most of a theme but want to adjust specific elements, such as using your brand color for user messages or increasing contrast on certain elements.
use agent_air::tui::themes::themes::get_theme;
use ratatui::style::{Color, Style};
fn modified_dark() -> Theme {
let mut theme = get_theme("dark").unwrap();
// Customize specific styles
theme.user_prefix = Style::default().fg(Color::Rgb(100, 200, 255));
theme.heading_1 = Style::default().fg(Color::Rgb(255, 100, 100));
theme
}
Using Themes in Widgets
Widgets receive the theme in their render method. Use theme properties for all styling to ensure visual consistency with the rest of the interface. Hardcoding colors breaks theme switching and creates visual inconsistency.
When building custom widgets, resist the temptation to use direct color values. The small additional effort of using theme properties pays off in consistent appearance and proper theme support.
fn render(&mut self, frame: &mut Frame, area: Rect, theme: &Theme) {
let block = Block::default()
.borders(Borders::ALL)
.border_style(theme.border);
let text = Paragraph::new("Hello")
.style(theme.text)
.block(block);
frame.render_widget(text, area);
}
Style Reference
Styles are built using Style::default() and method chaining. Understanding style construction helps when creating custom themes or troubleshooting appearance issues.
Styles combine foreground color, background color, and modifiers. You can set any combination of these properties, and unset properties inherit from the parent context.
use ratatui::style::{Color, Modifier, Style};
// Foreground color only
Style::default().fg(Color::Rgb(255, 128, 0))
// Background color only
Style::default().bg(Color::Rgb(30, 30, 30))
// Both colors
Style::default().fg(Color::White).bg(Color::Black)
// With bold modifier
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)
// Multiple modifiers
Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD | Modifier::ITALIC)
Available Modifiers
Modifiers change how text is rendered without affecting its color. Not all terminals support all modifiers; unsupported modifiers are typically ignored rather than causing errors.
| Modifier | Effect |
|---|---|
BOLD | Bold text |
ITALIC | Italic text |
UNDERLINED | Underlined text |
CROSSED_OUT | Strikethrough text |
DIM | Dimmed text |
REVERSED | Reversed foreground/background |
Thread Safety
The theme system uses RwLock for thread-safe access. Multiple widgets can read the theme simultaneously during rendering, while theme switching acquires an exclusive write lock. This design allows efficient concurrent reads while ensuring atomic updates.
In practice, you don’t need to worry about thread safety when using the theme functions. The locking is handled internally, and the API is designed to be safe in multithreaded contexts.
// Safe for concurrent reads
let theme = theme(); // Multiple widgets can call this
// Theme switching is exclusive
set_theme("light", new_theme); // Blocks until readers complete
If a lock becomes poisoned (rare, indicates a panic), functions return the default theme rather than panicking. This ensures the application can continue operating even in unusual circumstances.
Theme Picker Widget
The built-in theme picker provides an interactive way for users to switch themes. It displays all available themes with their names and applies selections immediately, giving users instant feedback on their choice.
The picker is an overlay widget that appears on top of the main content. Users navigate with arrow keys and select with Enter. Pressing Escape closes the picker without changing the theme.
The theme picker is included by default in the standard layout’s overlay widgets:
overlay_widget_ids: vec![
widget_ids::THEME_PICKER,
widget_ids::SESSION_PICKER,
],
Users access it through the /theme command.
