| Crates.io | ratatui-interact |
| lib.rs | ratatui-interact |
| version | 0.3.0 |
| created_at | 2026-01-19 23:23:30.492347+00 |
| updated_at | 2026-01-21 09:47:18.203641+00 |
| description | Interactive TUI components for ratatui with focus management and mouse support |
| homepage | https://github.com/Brainwires/ratatui-interact |
| repository | https://github.com/Brainwires/ratatui-interact |
| max_upload_size | |
| id | 2055496 |
| size | 1,143,299 |
Interactive TUI components for ratatui with focus management and mouse support.
Ratatui doesn't include built-in focus navigation or mouse click handling. This library fills that gap with ready-to-use interactive widgets and a flexible composition system.
FocusManager<T>ClickRegion and ClickRegionRegistryFocusable, Clickable, Container for building custom componentsAdd to your Cargo.toml:
[dependencies]
ratatui-interact = "0.3"
Or from git:
[dependencies]
ratatui-interact = { git = "https://github.com/Brainwires/ratatui-interact.git" }
use ratatui_interact::prelude::*;
// Define focusable elements
#[derive(Clone, Eq, PartialEq, Hash)]
enum Element {
NameInput,
EnableCheckbox,
SubmitButton,
}
// Set up focus manager
let mut focus = FocusManager::new();
focus.register(Element::NameInput);
focus.register(Element::EnableCheckbox);
focus.register(Element::SubmitButton);
// Create component states
let mut input_state = InputState::new("Hello");
let mut checkbox_state = CheckBoxState::new(false);
let button_state = ButtonState::enabled();
// Handle keyboard events
match event {
Event::Key(key) if is_tab(key) => focus.next(),
Event::Key(key) if is_shift_tab(key) => focus.prev(),
_ => {}
}
// Render with focus awareness
let input = Input::new(&input_state)
.label("Name")
.focused(focus.is_focused(&Element::NameInput));
let click_region = input.render_stateful(frame, area);
// Handle mouse clicks
if let Some(element) = click_region.contains(mouse_x, mouse_y) {
focus.focus(&element);
}
| Component | Description |
|---|---|
| CheckBox | Toggleable checkbox with multiple symbol styles (ASCII, Unicode, checkmark) |
| Input | Text input with cursor, insertion, deletion, and navigation |
| TextArea | Multi-line text input with cursor, line numbers, scrolling, and word wrap |
| Button | Multiple variants: SingleLine, Block, Toggle, Icon+Text |
| Select | Dropdown select box with popup options, keyboard/mouse navigation |
| ContextMenu | Right-click popup menu with actions, separators, shortcuts, and submenus |
| MenuBar | Traditional File/Edit/View/Help style menu bar with dropdowns, submenus, and shortcuts |
| PopupDialog | Container for modal dialogs with focus management |
| HotkeyDialog | Hotkey configuration dialog with search, categories, and trait-based customization |
| Component | Description |
|---|---|
| ParagraphExt | Extended paragraph with word-wrapping and scrolling |
| Toast | Transient notification popup with auto-expiration and style variants |
| Progress | Progress bar with label, percentage, and step counter |
| MarqueeText | Scrolling text for long content in limited space (continuous, bounce, static modes) |
| Spinner | Animated loading indicator with 12 frame styles (dots, braille, line, etc.) |
| MousePointer | Visual indicator at mouse cursor position with customizable styles |
| Component | Description |
|---|---|
| ListPicker | Scrollable list with selection cursor for picking items |
| TreeView | Collapsible tree view with selection and customizable rendering |
| FileExplorer | File browser with multi-select, search, and hidden file toggle |
| Accordion | Collapsible sections with single or multiple expansion modes |
| Breadcrumb | Hierarchical navigation path with ellipsis collapsing and keyboard/mouse support |
| Component | Description |
|---|---|
| TabView | Tab bar with content switching, supports top/bottom/left/right positions |
| SplitPane | Resizable split pane with drag-to-resize divider, horizontal/vertical orientations |
| Component | Description |
|---|---|
| LogViewer | Scrollable log viewer with line numbers, search, and log-level coloring |
| DiffViewer | Diff viewer with unified and side-by-side modes, hunk navigation, search, and syntax highlighting |
| StepDisplay | Multi-step progress display with sub-steps and output areas |
Parse ANSI escape codes to ratatui styles:
use ratatui_interact::utils::ansi::parse_ansi_to_spans;
let text = "\x1b[31mRed\x1b[0m Normal";
let spans = parse_ansi_to_spans(text);
Supports: SGR codes (bold, italic, colors), 256-color mode, RGB mode.
use ratatui_interact::utils::display::{
truncate_to_width, pad_to_width, clean_for_display, format_size
};
// Unicode-aware truncation with ellipsis
let truncated = truncate_to_width("Hello World", 8); // "Hello..."
// Unicode-aware padding
let padded = pad_to_width("Hi", 10); // "Hi "
// Clean text for display (strips ANSI, handles \r)
let clean = clean_for_display("\x1b[31mText\x1b[0m");
// Human-readable file sizes
let size = format_size(1536); // "1.5 KB"
use ratatui_interact::components::{Progress, ProgressStyle};
// From ratio (0.0 to 1.0)
let progress = Progress::new(0.75)
.label("Downloading")
.show_percentage(true);
// From step counts
let progress = Progress::from_steps(3, 10)
.label("Processing")
.show_steps(true);
// Different styles
let success = Progress::new(1.0).style(ProgressStyle::success());
let warning = Progress::new(0.9).style(ProgressStyle::warning());
use ratatui_interact::components::{Spinner, SpinnerState, SpinnerStyle, SpinnerFrames};
// Create state (call tick() each frame to animate)
let mut state = SpinnerState::new();
// Simple spinner
let spinner = Spinner::new(&state);
// With label
let spinner = Spinner::new(&state)
.label("Loading...");
// Different frame styles
let spinner = Spinner::new(&state)
.frames(SpinnerFrames::Braille)
.label("Processing");
// Custom color
let spinner = Spinner::new(&state)
.color(Color::Green)
.label("Success!");
// In your event loop, advance the animation
let frame_count = SpinnerFrames::Dots.frames().len();
state.tick_with_frames(frame_count);
// Or use state configured for specific frames
let mut state = SpinnerState::for_frames(SpinnerFrames::Moon);
state.tick_with_frames(SpinnerFrames::Moon.frames().len());
Available frame styles:
Dots - ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ (default)Braille - ⣾ ⣽ ⣻ ⢿ ⡿ ⣟ ⣯ ⣷Line - | / - \Circle - ◐ ◓ ◑ ◒Arrow - ← ↖ ↑ ↗ → ↘ ↓ ↙Clock - 🕐 🕑 🕒 ... (12 frames)Moon - 🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘Box, Bounce, Grow, Ascii, ToggleStyle presets: SpinnerStyle::success(), warning(), error(), info(), minimal()
use ratatui_interact::components::{MarqueeText, MarqueeState, MarqueeStyle, MarqueeMode};
// Create state (call tick() each frame to animate)
let mut state = MarqueeState::new();
// Continuous scrolling (loops around)
let marquee = MarqueeText::new("This is a long message that scrolls continuously", &mut state)
.style(MarqueeStyle::default().mode(MarqueeMode::Continuous));
// Bounce mode (scrolls back and forth) - great for file paths
let mut state = MarqueeState::new();
let marquee = MarqueeText::new("/home/user/very/long/path/to/file.rs", &mut state)
.style(MarqueeStyle::file_path());
// Static mode (truncate with ellipsis)
let mut state = MarqueeState::new();
let marquee = MarqueeText::new("Long text truncated with ellipsis", &mut state)
.style(MarqueeStyle::default().mode(MarqueeMode::Static));
// In your event loop, advance the animation
state.tick(text_width, viewport_width, &style);
Marquee modes:
Continuous - Text loops with a separator (default: " ")Bounce - Text scrolls to end, pauses, then scrolls backStatic - No animation, just truncate with ellipsisStyle presets:
MarqueeStyle::file_path() - Cyan, bounce mode, longer pauseMarqueeStyle::status() - Yellow bold, continuousMarqueeStyle::title() - Bold, bounce mode, long pauseuse ratatui_interact::components::{Select, SelectState, SelectStyle, handle_select_key, handle_select_mouse};
let options = vec!["Red", "Green", "Blue", "Yellow"];
let mut state = SelectState::new(options.len());
// Pre-select an option
let mut state = SelectState::with_selected(options.len(), 1); // "Green"
// Render the select box
let select = Select::new(&options, &state)
.label("Color")
.placeholder("Choose a color...");
let click_region = select.render_stateful(frame, area);
// Render dropdown when open (must be rendered last to appear on top)
let mut dropdown_regions = Vec::new();
if state.is_open {
dropdown_regions = select.render_dropdown(frame, area, screen_area);
}
// Handle keyboard (Enter/Space to open, Up/Down to navigate, Enter to select, Esc to close)
if let Some(action) = handle_select_key(&key_event, &mut state) {
match action {
SelectAction::Select(idx) => println!("Selected: {}", options[idx]),
_ => {}
}
}
// Handle mouse clicks
handle_select_mouse(&mouse_event, &mut state, area, &dropdown_regions);
Style presets:
SelectStyle::default() - Yellow highlight, checkmark indicatorSelectStyle::minimal() - Subtle yellow text highlightSelectStyle::arrow() - Arrow indicator (→)SelectStyle::bracket() - Bracket indicator ([x])use ratatui_interact::components::{
ContextMenu, ContextMenuItem, ContextMenuState, ContextMenuStyle,
handle_context_menu_key, handle_context_menu_mouse, is_context_menu_trigger,
};
// Create menu items with actions, separators, and submenus
let items = vec![
ContextMenuItem::action("open", "Open").icon("📂").shortcut("Enter"),
ContextMenuItem::action("edit", "Edit").icon("✏️").shortcut("E"),
ContextMenuItem::separator(),
ContextMenuItem::action("copy", "Copy").icon("📋").shortcut("Ctrl+C"),
ContextMenuItem::action("paste", "Paste").icon("📄").enabled(false), // Disabled
ContextMenuItem::separator(),
ContextMenuItem::submenu("More", vec![
ContextMenuItem::action("new_file", "New File").icon("📄"),
ContextMenuItem::action("new_folder", "New Folder").icon("📁"),
]).icon("➕"),
ContextMenuItem::separator(),
ContextMenuItem::action("delete", "Delete").icon("🗑️").shortcut("Del"),
];
// Create state
let mut state = ContextMenuState::new();
// Open menu on right-click
if is_context_menu_trigger(&mouse_event) {
state.open_at(mouse_event.column, mouse_event.row);
}
// Render the menu (must be rendered last to appear on top)
if state.is_open {
let menu = ContextMenu::new(&items, &state)
.style(ContextMenuStyle::default());
let (menu_area, click_regions) = menu.render_stateful(frame, screen_area);
}
// Handle keyboard (Up/Down to navigate, Enter to select, Esc to close, Right for submenu)
if let Some(action) = handle_context_menu_key(&key_event, &mut state, &items) {
match action {
ContextMenuAction::Select(id) => println!("Selected: {}", id),
ContextMenuAction::Close => println!("Menu closed"),
_ => {}
}
}
// Handle mouse clicks
handle_context_menu_mouse(&mouse_event, &mut state, menu_area, &click_regions);
Key bindings:
Up/Down: Navigate items (skips separators)Enter/Space: Select item or open submenuRight: Open submenuLeft/Esc: Close submenu or close menuHome/End: Jump to first/last itemStyle presets:
ContextMenuStyle::default() - Dark theme with blue highlightContextMenuStyle::light() - Light themeContextMenuStyle::minimal() - Simple style with reset backgrounduse ratatui_interact::components::{
Menu, MenuBar, MenuBarItem, MenuBarState, MenuBarStyle,
handle_menu_bar_key, handle_menu_bar_mouse,
};
// Create menus with items, separators, shortcuts, and submenus
let menus = vec![
Menu::new("File").items(vec![
MenuBarItem::action("new", "New").shortcut("Ctrl+N"),
MenuBarItem::action("open", "Open...").shortcut("Ctrl+O"),
MenuBarItem::separator(),
MenuBarItem::action("save", "Save").shortcut("Ctrl+S"),
MenuBarItem::submenu("Export", vec![
MenuBarItem::action("export_pdf", "Export as PDF"),
MenuBarItem::action("export_html", "Export as HTML"),
]),
MenuBarItem::separator(),
MenuBarItem::action("quit", "Quit").shortcut("Ctrl+Q"),
]),
Menu::new("Edit").items(vec![
MenuBarItem::action("undo", "Undo").shortcut("Ctrl+Z"),
MenuBarItem::action("redo", "Redo").shortcut("Ctrl+Y"),
MenuBarItem::separator(),
MenuBarItem::action("cut", "Cut").shortcut("Ctrl+X"),
MenuBarItem::action("copy", "Copy").shortcut("Ctrl+C"),
MenuBarItem::action("paste", "Paste").shortcut("Ctrl+V").enabled(false), // Disabled
]),
];
// Create state
let mut state = MenuBarState::new();
state.focused = true;
// Render the menu bar
let menu_bar = MenuBar::new(&menus, &state)
.style(MenuBarStyle::default());
let (bar_area, dropdown_area, click_regions) = menu_bar.render_stateful(frame, area);
// Handle keyboard (arrows navigate, Enter selects, Esc closes)
if let Some(action) = handle_menu_bar_key(&key_event, &mut state, &menus) {
match action {
MenuBarAction::ItemSelect(id) => println!("Selected: {}", id),
MenuBarAction::MenuOpen(idx) => println!("Menu {} opened", idx),
MenuBarAction::MenuClose => println!("Menu closed"),
_ => {}
}
}
// Handle mouse (click to open, hover to switch menus)
handle_menu_bar_mouse(&mouse_event, &mut state, bar_area, dropdown_area, &click_regions, &menus);
Key bindings:
Left/Right: Navigate between menusUp/Down: Navigate items in dropdown (opens menu if closed)Enter/Space: Select item or toggle menuRight (on submenu): Open submenuLeft/Esc: Close submenu or close menuHome/End: Jump to first/last itemStyle presets:
MenuBarStyle::default() - Dark themeMenuBarStyle::light() - Light themeMenuBarStyle::minimal() - Simple style with reset backgrounduse ratatui_interact::components::{MousePointer, MousePointerState, MousePointerStyle};
// Create state (disabled by default)
let mut state = MousePointerState::default();
// Enable and update position from mouse events
state.set_enabled(true);
state.update_position(mouse.column, mouse.row);
// Create pointer with custom style
let pointer = MousePointer::new(&state)
.style(MousePointerStyle::crosshair());
// Render LAST to appear on top of other widgets
pointer.render(frame.buffer_mut());
// Toggle visibility
state.toggle();
Style presets:
MousePointerStyle::default() - Yellow block (█)MousePointerStyle::crosshair() - Cyan crosshair (┼)MousePointerStyle::arrow() - White arrow (▶)MousePointerStyle::dot() - Green dot (●)MousePointerStyle::plus() - Magenta plus (+)MousePointerStyle::custom(symbol, color) - User-definedCustom styling:
let style = MousePointerStyle::default()
.symbol("◆")
.fg(Color::Rgb(255, 128, 0)) // Orange
.bg(Color::DarkGray);
use ratatui_interact::components::{ListPicker, ListPickerState};
use ratatui::text::Line;
let items = vec!["Option A", "Option B", "Option C"];
let mut state = ListPickerState::new(items.len());
// Navigate
state.select_next();
state.select_prev();
// Custom rendering
let picker = ListPicker::new(&items, &state)
.title("Select Option")
.render_item(|item, _idx, selected| {
vec![Line::from(item.to_string())]
});
use ratatui_interact::components::{TreeView, TreeViewState, TreeNode};
#[derive(Clone, Debug)]
struct Task { name: String, done: bool }
let nodes = vec![
TreeNode::new("1", Task { name: "Build".into(), done: false })
.with_children(vec![
TreeNode::new("1.1", Task { name: "Compile".into(), done: true }),
TreeNode::new("1.2", Task { name: "Link".into(), done: false }),
]),
];
let mut state = TreeViewState::new();
state.toggle_collapsed("1"); // Collapse/expand
let tree = TreeView::new(&nodes, &state)
.render_item(|node, selected| {
format!("[{}] {}", if node.data.done { "x" } else { " " }, node.data.name)
});
use ratatui_interact::components::{Accordion, AccordionState, AccordionMode};
// Single mode: only one section expanded at a time (FAQ-style)
let mut state = AccordionState::new(items.len())
.with_mode(AccordionMode::Single);
// Multiple mode: any number can be expanded (settings-style)
let mut state = AccordionState::new(items.len())
.with_mode(AccordionMode::Multiple)
.with_expanded(vec!["section1".into()]);
// Toggle, expand, collapse
state.toggle("faq1");
state.expand("faq2");
state.collapse("faq1");
// Create accordion with custom renderers
let accordion = Accordion::new(&items, &state)
.id_fn(|item, _| item.id.clone())
.render_header(|item, _idx, is_focused| {
Line::raw(item.title.clone())
})
.render_content(|item, _idx, area, buf| {
let paragraph = Paragraph::new(item.content.as_str());
paragraph.render(area, buf);
});
use ratatui_interact::components::{
Breadcrumb, BreadcrumbItem, BreadcrumbState, BreadcrumbStyle,
handle_breadcrumb_key, handle_breadcrumb_mouse,
};
// Create breadcrumb items with optional icons
let items = vec![
BreadcrumbItem::new("home", "Home").icon("🏠"),
BreadcrumbItem::new("users", "Users"),
BreadcrumbItem::new("profile", "Profile Settings"),
];
// Create state
let mut state = BreadcrumbState::new(items);
state.focused = true;
// Create breadcrumb with default style (uses " > " separator)
let breadcrumb = Breadcrumb::new(&state);
let click_regions = breadcrumb.render_stateful(area, buf);
// Different style presets:
// - BreadcrumbStyle::slash() - " / " (Unix path style)
// - BreadcrumbStyle::chevron() - " › " (Unicode chevron)
// - BreadcrumbStyle::arrow() - " → " (Unicode arrow)
// - BreadcrumbStyle::minimal() - Subdued colors
let breadcrumb = Breadcrumb::new(&state)
.style(BreadcrumbStyle::chevron());
// Handle keyboard (arrows navigate, Enter activates, e expands ellipsis)
if let Some(action) = handle_breadcrumb_key(&key_event, &mut state) {
match action {
BreadcrumbAction::Navigate(id) => println!("Navigate to: {}", id),
BreadcrumbAction::ExpandEllipsis => println!("Ellipsis toggled"),
}
}
// Handle mouse clicks
handle_breadcrumb_mouse(&mouse_event, &mut state, &click_regions);
// Dynamic path manipulation
state.push(BreadcrumbItem::new("new_item", "New Item"));
state.pop();
state.clear();
Ellipsis collapsing: Long paths automatically collapse with ... (configurable threshold).
Example: Home > ... > Settings > Profile when showing 7+ items.
use ratatui_interact::components::{
Tab, TabView, TabViewState, TabViewStyle, TabPosition,
handle_tab_view_key, handle_tab_view_mouse,
};
use ratatui_interact::traits::ClickRegionRegistry;
// Create tabs with optional icons and badges
let tabs = vec![
Tab::new("General").icon("⚙"),
Tab::new("Network").icon("🌐").badge("3"),
Tab::new("Security").icon("🔒"),
];
// Create state
let mut state = TabViewState::new(tabs.len());
// Create style (tabs on left side)
let style = TabViewStyle::left().tab_width(18);
// Create tab view with content renderer
let tab_view = TabView::new(&tabs, &state)
.style(style)
.content(|idx, area, buf| {
let text = match idx {
0 => "General settings content",
1 => "Network configuration content",
_ => "Security options content",
};
Paragraph::new(text).render(area, buf);
});
// Render and register click regions
let mut registry: ClickRegionRegistry<TabViewAction> = ClickRegionRegistry::new();
tab_view.render_with_registry(area, buf, &mut registry);
// Handle keyboard (arrows navigate, Enter focuses content, Esc focuses tabs, 1-9 direct select)
handle_tab_view_key(&mut state, &key_event, style.position);
// Handle mouse clicks
handle_tab_view_mouse(&mut state, ®istry, &mouse_event);
Style presets:
TabViewStyle::top() - Horizontal tabs above content (default)TabViewStyle::bottom() - Horizontal tabs below contentTabViewStyle::left() - Vertical tabs on left sideTabViewStyle::right() - Vertical tabs on right sideTabViewStyle::minimal() - No borders, simple dividersuse ratatui_interact::components::{
SplitPane, SplitPaneState, SplitPaneStyle, SplitPaneAction, Orientation,
handle_split_pane_key, handle_split_pane_mouse,
};
use ratatui_interact::traits::ClickRegionRegistry;
// Create state with initial split percentage (50% = equal split)
let mut state = SplitPaneState::new(50);
state.divider_focused = true; // Enable keyboard resize
// Create split pane with horizontal orientation (left | right)
let split_pane = SplitPane::new(&state)
.orientation(Orientation::Horizontal)
.style(SplitPaneStyle::default())
.min_percent(10) // Minimum 10% for first pane
.max_percent(90); // Maximum 90% for first pane
// Calculate areas for manual rendering
let (first_area, divider_area, second_area) = split_pane.calculate_areas(area);
// Register click regions for mouse support
let mut registry: ClickRegionRegistry<SplitPaneAction> = ClickRegionRegistry::new();
registry.register(first_area, SplitPaneAction::FirstPaneClick);
registry.register(divider_area, SplitPaneAction::DividerDrag);
registry.register(second_area, SplitPaneAction::SecondPaneClick);
// Or use the all-in-one render method with closures
split_pane.render_with_content(
area,
buf,
&mut state,
|first_area, buf| { /* render first pane content */ },
|second_area, buf| { /* render second pane content */ },
&mut registry,
);
// Handle keyboard (arrows resize when divider focused, Home/End for min/max)
handle_split_pane_key(&mut state, &key_event, Orientation::Horizontal, 5, 10, 90);
// Handle mouse (drag divider to resize)
handle_split_pane_mouse(&mut state, &mouse_event, Orientation::Horizontal, ®istry, 10, 90);
Orientations:
Orientation::Horizontal - Left | Right split (default)Orientation::Vertical - Top / Bottom splitStyle presets:
SplitPaneStyle::default() - Dark gray divider with grab indicatorSplitPaneStyle::minimal() - Thin line divider, no backgroundSplitPaneStyle::prominent() - Blue divider with high visibilityuse ratatui_interact::components::{LogViewer, LogViewerState};
let logs = vec![
"[INFO] Application started".to_string(),
"[ERROR] Connection failed".to_string(),
];
let mut state = LogViewerState::new(logs);
// Search
state.search("ERROR");
state.next_match();
// Scroll
state.scroll_down(5);
state.scroll_right(10);
let viewer = LogViewer::new(&state)
.title("Application Log")
.show_line_numbers(true);
use ratatui_interact::components::{
DiffViewer, DiffViewerState, DiffViewMode, DiffData,
handle_diff_viewer_key, handle_diff_viewer_mouse,
};
// Parse a unified diff (e.g., from `git diff`)
let diff_text = r#"--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
fn main() {
- println!("Hello, world!");
+ println!("Hello, Rust!");
+ println!("Welcome!");
}
"#;
let mut state = DiffViewerState::from_unified_diff(diff_text);
// Toggle view mode
state.toggle_view_mode(); // Switches between Unified and SideBySide
// Hunk navigation
state.next_hunk();
state.prev_hunk();
// Change navigation (jump to next/prev addition or deletion)
state.next_change();
state.prev_change();
// Search within diff
state.start_search();
state.search.query = "println".to_string();
state.update_search();
state.next_match();
// Create viewer
let viewer = DiffViewer::new(&state)
.title("Code Changes")
.show_stats(true); // Shows +/- counts in title
// Handle keyboard input (in your event loop)
// handle_diff_viewer_key(&mut state, &key_event);
// Handle mouse scroll
// handle_diff_viewer_mouse(&mut state, &mouse_event);
Key bindings:
j/k or ↑/↓: Scroll up/downh/l or ←/→: Scroll left/rightg/G or Home/End: Go to top/bottom]/[: Next/previous hunkn/N: Next/previous change (or search match)v or m: Toggle view mode (unified/side-by-side)/: Start searchPgUp/PgDn or Ctrl+U/D: Page navigationStyle presets:
DiffViewerStyle::default() - Green additions, red deletions with dark backgroundsDiffViewerStyle::high_contrast() - Brighter colors for better visibilityDiffViewerStyle::monochrome() - Bold/dim text without colorsuse ratatui_interact::components::{Step, StepDisplayState, StepDisplay, StepStatus};
let steps = vec![
Step::new("Initialize").with_sub_steps(vec!["Load config", "Connect DB"]),
Step::new("Process data"),
Step::new("Finalize"),
];
let mut state = StepDisplayState::new(steps);
// Update progress
state.start_step(0);
state.start_sub_step(0, 0);
state.complete_sub_step(0, 0);
state.add_output(0, "Config loaded successfully");
state.complete_step(0);
let display = StepDisplay::new(&state);
use ratatui_interact::components::{FileExplorerState, FileExplorer};
use std::path::PathBuf;
let mut state = FileExplorerState::new(PathBuf::from("/home/user"));
// Navigate
state.cursor_down();
state.cursor_up();
state.toggle_selection(); // Multi-select
state.toggle_hidden(); // Show/hide hidden files
// Enter search mode
state.start_search();
state.search_push('r'); // Filter by 'r'
let explorer = FileExplorer::new(&state)
.title("Select Files")
.show_hidden(true);
Toast notifications provide transient feedback to users:
use ratatui_interact::components::{Toast, ToastState, ToastStyle};
struct App {
toast_state: ToastState,
}
// Show a toast for 3 seconds
app.toast_state.show("File saved successfully!", 3000);
// In your render function:
fn render(app: &mut App, frame: &mut Frame, area: Rect) {
// Draw your main content first...
// Then draw toast on top if visible
if let Some(message) = app.toast_state.get_message() {
Toast::new(message)
.style(ToastStyle::Success)
.render_with_clear(area, frame.buffer_mut());
}
}
// In your event loop, periodically clear expired toasts
app.toast_state.clear_if_expired();
Toast styles are auto-detected from message content, or can be set explicitly:
ToastStyle::Info (cyan) - defaultToastStyle::Success (green) - messages containing "success", "saved", "done"ToastStyle::Warning (yellow) - messages containing "warning", "warn"ToastStyle::Error (red) - messages containing "error", "fail"Buttons support mouse clicks through click regions. Use render_with_registry() for the simplest pattern:
use ratatui_interact::components::{Button, ButtonState};
use ratatui_interact::traits::ClickRegionRegistry;
struct App {
click_regions: ClickRegionRegistry<usize>,
// ... other fields
}
// In your render function:
fn render(app: &mut App, frame: &mut Frame) {
// Clear at start of each frame
app.click_regions.clear();
let state = ButtonState::enabled();
let button = Button::new("OK", &state);
// Render and register in one call
button.render_with_registry(area, frame.buffer_mut(), &mut app.click_regions, 0);
}
// In your event handler:
fn handle_mouse(app: &App, mouse: MouseEvent) {
if is_left_click(&mouse) {
if let Some(&idx) = app.click_regions.handle_click(mouse.column, mouse.row) {
// Button at index `idx` was clicked
}
}
}
For more control, use the two-step pattern with render_stateful():
let region = button.render_stateful(area, buf);
registry.register(region.area, my_custom_action);
Run the examples to see components in action:
# Interactive Components
cargo run --example checkbox_demo # Checkbox with multiple styles
cargo run --example input_demo # Text input with cursor
cargo run --example textarea_demo # Multi-line text input
cargo run --example button_demo # Button variants and styles
cargo run --example select_demo # Dropdown select boxes
cargo run --example context_menu_demo # Right-click context menus
cargo run --example menu_bar_demo # File/Edit/View/Help style menu bar
cargo run --example dialog_demo # Modal dialogs
cargo run --example hotkey_dialog_demo # Hotkey configuration dialog
# Display & Viewer Components
cargo run --example marquee_demo # Scrolling text animation
cargo run --example mouse_pointer_demo # Mouse cursor indicator
cargo run --example spinner_demo # Animated loading indicators
cargo run --example display_demo # Progress, StepDisplay, ParagraphExt
cargo run --example diff_viewer_demo # Diff viewer with unified/side-by-side modes
# Navigation & Layout Components
cargo run --example accordion_demo # Collapsible sections
cargo run --example tab_view_demo # Tab bar with positions
cargo run --example split_pane_demo # Resizable split panes
cargo run --example breadcrumb_demo # Hierarchical path navigation
cargo run --example navigation_demo # ListPicker and TreeView
# Combined Demo (requires filesystem feature)
cargo run --example explorer_log_demo --features filesystem # FileExplorer + LogViewer + Toast
| Feature | ratatui-interact | rat-focus | tui-input |
|---|---|---|---|
| Focus management | ✅ Generic FocusManager<T> |
✅ FocusFlag based |
❌ |
| Mouse click regions | ✅ ClickRegion with hit-testing |
✅ Area-based | ❌ |
| Ready-to-use widgets | ✅ Many (see above) | ❌ | ✅ Input only |
| Composition traits | ✅ Focusable, Clickable, Container | ❌ | ❌ |
MIT