| name | tui-screen |
| description | Create TUI screens, components, and views for the agents-in-a-box terminal UI. Use when building new ratatui components, adding panels, creating list views, or styling any terminal interface element. Provides color palette, component patterns, and quality checklist. |
TUI Screen Creation Skill
This skill guides creation of premium, consistent TUI components for agents-in-a-box.
Core Principles
All TUI components must follow these styling standards for visual consistency:
1. Color Palette (MANDATORY)
const CORNFLOWER_BLUE: Color = Color::Rgb(100, 149, 237);
const GOLD: Color = Color::Rgb(255, 215, 0);
const SELECTION_GREEN: Color = Color::Rgb(100, 200, 100);
const WARNING_ORANGE: Color = Color::Rgb(255, 165, 0);
const DARK_BG: Color = Color::Rgb(25, 25, 35);
const PANEL_BG: Color = Color::Rgb(30, 30, 40);
const LIST_HIGHLIGHT_BG: Color = Color::Rgb(40, 40, 60);
const SOFT_WHITE: Color = Color::Rgb(220, 220, 230);
const MUTED_GRAY: Color = Color::Rgb(120, 120, 140);
const SUBDUED_BORDER: Color = Color::Rgb(60, 60, 80);
const PROGRESS_CYAN: Color = Color::Rgb(100, 200, 230);
2. Required Imports
use ratatui::{
Frame,
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, BorderType, List, ListItem, ListState, Paragraph, Tabs, Wrap},
};
Component Patterns
Standard Panel Block
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(CORNFLOWER_BLUE))
.style(Style::default().bg(DARK_BG))
.title(Line::from(vec![
Span::styled(" 📁 ", Style::default().fg(GOLD)),
Span::styled("Title", Style::default().fg(GOLD).add_modifier(Modifier::BOLD)),
]))
Active/Focused Panel
.border_style(Style::default().fg(SELECTION_GREEN))
Title with Count Badge
.title(Line::from(vec![
Span::styled(" 📁 ", Style::default().fg(GOLD)),
Span::styled("Items ", Style::default().fg(GOLD).add_modifier(Modifier::BOLD)),
Span::styled(format!("({})", count), Style::default().fg(CORNFLOWER_BLUE).add_modifier(Modifier::BOLD)),
]))
Bottom Help Bar (Keyboard Hints)
.title_bottom(Line::from(vec![
Span::styled(" Enter", Style::default().fg(GOLD).add_modifier(Modifier::BOLD)),
Span::styled(" select ", Style::default().fg(MUTED_GRAY)),
Span::styled("│", Style::default().fg(SUBDUED_BORDER)),
Span::styled(" Esc", Style::default().fg(GOLD).add_modifier(Modifier::BOLD)),
Span::styled(" back ", Style::default().fg(MUTED_GRAY)),
]))
Selected List Item
let mut spans = vec![];
if is_selected {
spans.push(Span::styled("▶ ", Style::default().fg(SELECTION_GREEN)));
} else {
spans.push(Span::raw(" "));
}
spans.push(Span::styled(&item_text,
if is_selected {
Style::default().fg(SELECTION_GREEN).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(SOFT_WHITE)
}
));
let base_style = if is_selected {
Style::default().bg(LIST_HIGHLIGHT_BG)
} else {
Style::default()
};
ListItem::new(Line::from(spans)).style(base_style)
Empty State
Paragraph::new(vec![
Line::from(Span::styled("✨ No items found", Style::default().fg(MUTED_GRAY))),
Line::from(""),
Line::from(Span::styled("Helpful hint here", Style::default().fg(MUTED_GRAY).add_modifier(Modifier::ITALIC))),
])
Input Field (Active)
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(SELECTION_GREEN))
.style(Style::default().bg(Color::Rgb(35, 35, 45)))
Span::styled("█", Style::default().fg(SELECTION_GREEN))
Status Bar
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(SUBDUED_BORDER))
.style(Style::default().bg(PANEL_BG))
Status Indicator Pattern
Use for displaying configuration/connection status inline with settings:
let (status_text, status_style) = if is_configured {
(
format!("{} ({})", config_type, masked_value),
Style::default().fg(SELECTION_GREEN)
)
} else {
(
"Not configured".to_string(),
Style::default().fg(MUTED_GRAY)
)
};
Line::from(vec![
Span::styled("▶ ", Style::default().fg(SELECTION_GREEN)),
Span::styled("Setting Name", Style::default().fg(SOFT_WHITE)),
Span::styled(": ", Style::default().fg(MUTED_GRAY)),
Span::styled(status_text, status_style),
])
Status Badge Pattern
For showing status badges next to items (e.g., "Coming Soon", "Beta", "Current"):
const COMING_SOON_GRAY: Color = Color::Rgb(80, 80, 100);
const BETA_CYAN: Color = Color::Rgb(100, 200, 230);
const CURRENT_GREEN: Color = Color::Rgb(100, 200, 100);
fn status_badge(text: &str, badge_type: BadgeType) -> Span {
let (color, modifier) = match badge_type {
BadgeType::ComingSoon => (COMING_SOON_GRAY, Modifier::ITALIC),
BadgeType::Beta => (BETA_CYAN, Modifier::empty()),
BadgeType::Current => (CURRENT_GREEN, Modifier::empty()),
BadgeType::Disabled => (MUTED_GRAY, Modifier::ITALIC),
};
Span::styled(
format!("[{}]", text),
Style::default().fg(color).add_modifier(modifier)
)
}
let mut spans = vec![
Span::styled("Item Name", Style::default().fg(SOFT_WHITE)),
Span::styled(" ", Style::default()),
];
if is_coming_soon {
spans.push(status_badge("Coming Soon", BadgeType::ComingSoon));
}
if is_current {
spans.push(Span::styled(" ", Style::default()));
spans.push(status_badge("Current", BadgeType::Current));
}
Configuration Status Panel
For showing multiple status items in a summary view:
fn render_status_row(label: &str, value: &str, is_ok: bool) -> Line<'static> {
let indicator = if is_ok {
Span::styled("✓ ", Style::default().fg(SELECTION_GREEN))
} else {
Span::styled("○ ", Style::default().fg(MUTED_GRAY))
};
Line::from(vec![
indicator,
Span::styled(label, Style::default().fg(SOFT_WHITE)),
Span::styled(": ", Style::default().fg(MUTED_GRAY)),
Span::styled(
value.to_string(),
if is_ok {
Style::default().fg(SOFT_WHITE)
} else {
Style::default().fg(MUTED_GRAY).add_modifier(Modifier::ITALIC)
}
),
])
}
let rows = vec![
render_status_row("Claude Auth", "API Key (sk-ant-••••)", true),
render_status_row("GitHub", "System Default", true),
render_status_row("AWS Bedrock", "Not configured", false),
];
Icon Reference
| Element | Icon | Color |
|---|
| Folder closed | 📁 | CORNFLOWER_BLUE |
| Folder open | 📂 | CORNFLOWER_BLUE |
| File generic | 📄 | SOFT_WHITE |
| File Rust | 🦀 | - |
| File Python | 🐍 | - |
| File Markdown | 📝 | - |
| Config | ⚙️ | - |
| Success/Check | ✓ | SELECTION_GREEN |
| Running | 🟢 | Green |
| Stopped | 🔴 | Red |
| Warning | ⚠️ | WARNING_ORANGE |
| Loading | 🔄 | WARNING_ORANGE |
| Push ready | 🚀 | SELECTION_GREEN |
| Edit | ✏️ | GOLD |
| Stats | 📊 | GOLD |
| Sparkle | ✨ | GOLD |
Tree Characters
const EXPANDED: &str = "▼";
const COLLAPSED: &str = "▶";
const TREE_BRANCH: &str = "├─";
const TREE_LAST: &str = "└─";
const TREE_VERTICAL: &str = "│";
Layout Guidelines
| Element | Constraint |
|---|
| Tab bar | Length(3) |
| Status bar | Length(3) |
| Content area | Min(0) |
| Modal width | 80% of terminal |
| Modal height | 70% of terminal |
| Split panes | 40/60 or 50/50 |
Component Structure Template
pub struct MyComponentState {
pub selected_index: usize,
pub items: Vec<MyItem>,
pub scroll_offset: usize,
pub is_active: bool,
}
impl MyComponentState {
pub fn new() -> Self { }
pub fn next_item(&mut self) { }
pub fn previous_item(&mut self) { }
}
pub struct MyComponent;
impl MyComponent {
pub fn render(frame: &mut Frame, area: Rect, state: &MyComponentState) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Min(0),
Constraint::Length(3),
])
.split(area);
Self::render_header(frame, chunks[0], state);
Self::render_content(frame, chunks[1], state);
Self::render_status_bar(frame, chunks[2], state);
}
}
Modal Popup Patterns (MANDATORY for Config/Settings)
All configuration and settings screens MUST use popup-based interactions rather than inline editing:
Popup Types
| Setting Type | Popup Behavior |
|---|
| Choice/Enum | Selection list with ▶ indicator, Up/Down navigation |
| Text/String | Text input field with cursor |
| Boolean | Enabled/Disabled toggle selection |
| Number | Numeric input field |
Popup Structure
use ratatui::widgets::Clear;
let popup_width = 50u16.min(area.width - 4);
let popup_height = 12u16.min(area.height - 4);
let popup_x = (area.width - popup_width) / 2;
let popup_y = (area.height - popup_height) / 2;
let popup_area = Rect::new(popup_x, popup_y, popup_width, popup_height);
frame.render_widget(Clear, popup_area);
let block = Block::default()
.title(Span::styled(
format!(" {} ", title),
Style::default().fg(GOLD).add_modifier(Modifier::BOLD),
))
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(CORNFLOWER_BLUE))
.style(Style::default().bg(PANEL_BG));
Choice Selection Popup
let indicator = if is_selected { "▶ " } else { " " };
let style = if is_selected {
Style::default()
.fg(SELECTION_GREEN)
.bg(LIST_HIGHLIGHT_BG)
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(SOFT_WHITE)
};
Line::from(vec![
Span::styled(indicator, Style::default().fg(SELECTION_GREEN)),
Span::styled(option_text, style),
])
Boolean Toggle Popup
Line::from(vec![
Span::styled(if value { "▶ " } else { " " }, Style::default().fg(SELECTION_GREEN)),
Span::styled("✓ Enabled", if value { selected_style } else { normal_style }),
]),
Line::from(vec![
Span::styled(if !value { "▶ " } else { " " }, Style::default().fg(SELECTION_GREEN)),
Span::styled("✗ Disabled", if !value { selected_style } else { normal_style }),
]),
Text Input Popup
let input_block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(GOLD))
.style(Style::default().bg(LIST_HIGHLIGHT_BG));
let display = format!("{}|", value);
Popup Help Bar
let help_items = vec![
("↑↓", "select"),
("Enter", "confirm"),
("Esc", "cancel"),
];
let help_items = vec![
("Enter", "save"),
("Esc", "cancel"),
];
Reference Implementation
See src/components/config_popup.rs for complete popup component with:
ConfigPopupType enum (Choice, TextInput, Boolean, NumberInput)
ConfigPopupState for state management
- Full render implementation
Quality Checklist
Before completing any TUI component:
Reference Components
Study these for patterns:
src/components/git_view.rs - Tabs, lists, tree view, input, markdown
src/components/session_detail.rs - Panel layouts, status indicators
src/components/new_session_panel.rs - Modal dialogs, card selection