ratatui-interact
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.
Features
- Focus Management - Tab/Shift+Tab navigation with
FocusManager<T> - Mouse Click Support - Click regions with hit-testing via
ClickRegionandClickRegionRegistry - Interactive Widgets - CheckBox, Input, Button, Select, ContextMenu, MenuBar, PopupDialog
- Display Widgets - ParagraphExt, Toast, Progress, MarqueeText, Spinner, MousePointer
- Navigation Widgets - ListPicker, TreeView, FileExplorer, Accordion
- Layout Widgets - TabView, SplitPane
- Viewer Widgets - LogViewer, DiffViewer, StepDisplay
- Utilities - ANSI parsing, display helpers
- Composition Traits -
Focusable,Clickable,Containerfor building custom components
Installation
Add to your Cargo.toml:
[]
= "0.3"
Or from git:
[]
= { = "https://github.com/Brainwires/ratatui-interact.git" }
Quick Start
use *;
// Define focusable elements
// Set up focus manager
let mut focus = new;
focus.register;
focus.register;
focus.register;
// Create component states
let mut input_state = new;
let mut checkbox_state = new;
let button_state = enabled;
// Handle keyboard events
match event
// Render with focus awareness
let input = new
.label
.focused;
let click_region = input.render_stateful;
// Handle mouse clicks
if let Some = click_region.contains
Components
Interactive Components
| 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 |
Display Components
| 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 |
Navigation Components
| 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 |
Layout Components
| 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 |
Viewer Components
| 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 |
Utilities
ANSI Parser
Parse ANSI escape codes to ratatui styles:
use parse_ansi_to_spans;
let text = "\x1b[31mRed\x1b[0m Normal";
let spans = parse_ansi_to_spans;
Supports: SGR codes (bold, italic, colors), 256-color mode, RGB mode.
Display Utilities
use ;
// Unicode-aware truncation with ellipsis
let truncated = truncate_to_width; // "Hello..."
// Unicode-aware padding
let padded = pad_to_width; // "Hi "
// Clean text for display (strips ANSI, handles \r)
let clean = clean_for_display;
// Human-readable file sizes
let size = format_size; // "1.5 KB"
Component Examples
Progress Bar
use ;
// From ratio (0.0 to 1.0)
let progress = new
.label
.show_percentage;
// From step counts
let progress = from_steps
.label
.show_steps;
// Different styles
let success = new.style;
let warning = new.style;
Spinner
use ;
// Create state (call tick() each frame to animate)
let mut state = new;
// Simple spinner
let spinner = new;
// With label
let spinner = new
.label;
// Different frame styles
let spinner = new
.frames
.label;
// Custom color
let spinner = new
.color
.label;
// In your event loop, advance the animation
let frame_count = Dots.frames.len;
state.tick_with_frames;
// Or use state configured for specific frames
let mut state = for_frames;
state.tick_with_frames;
Available frame styles:
Dots- ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏ (default)Braille- ⣾ ⣽ ⣻ ⢿ ⡿ ⣟ ⣯ ⣷Line- | / - \Circle- ◐ ◓ ◑ ◒Arrow- ← ↖ ↑ ↗ → ↘ ↓ ↙Clock- 🕐 🕑 🕒 ... (12 frames)Moon- 🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘- And more:
Box,Bounce,Grow,Ascii,Toggle
Style presets: SpinnerStyle::success(), warning(), error(), info(), minimal()
Marquee Text
use ;
// Create state (call tick() each frame to animate)
let mut state = new;
// Continuous scrolling (loops around)
let marquee = new
.style;
// Bounce mode (scrolls back and forth) - great for file paths
let mut state = new;
let marquee = new
.style;
// Static mode (truncate with ellipsis)
let mut state = new;
let marquee = new
.style;
// In your event loop, advance the animation
state.tick;
Marquee modes:
Continuous- Text loops with a separator (default: " ")Bounce- Text scrolls to end, pauses, then scrolls backStatic- No animation, just truncate with ellipsis
Style presets:
MarqueeStyle::file_path()- Cyan, bounce mode, longer pauseMarqueeStyle::status()- Yellow bold, continuousMarqueeStyle::title()- Bold, bounce mode, long pause
Select (Dropdown)
use ;
let options = vec!;
let mut state = new;
// Pre-select an option
let mut state = with_selected; // "Green"
// Render the select box
let select = new
.label
.placeholder;
let click_region = select.render_stateful;
// Render dropdown when open (must be rendered last to appear on top)
let mut dropdown_regions = Vecnew;
if state.is_open
// Handle keyboard (Enter/Space to open, Up/Down to navigate, Enter to select, Esc to close)
if let Some = handle_select_key
// Handle mouse clicks
handle_select_mouse;
Style presets:
SelectStyle::default()- Yellow highlight, checkmark indicatorSelectStyle::minimal()- Subtle yellow text highlightSelectStyle::arrow()- Arrow indicator (→)SelectStyle::bracket()- Bracket indicator ([x])
Context Menu
use ;
// Create menu items with actions, separators, and submenus
let items = vec!;
// Create state
let mut state = new;
// Open menu on right-click
if is_context_menu_trigger
// Render the menu (must be rendered last to appear on top)
if state.is_open
// Handle keyboard (Up/Down to navigate, Enter to select, Esc to close, Right for submenu)
if let Some = handle_context_menu_key
// Handle mouse clicks
handle_context_menu_mouse;
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 item
Style presets:
ContextMenuStyle::default()- Dark theme with blue highlightContextMenuStyle::light()- Light themeContextMenuStyle::minimal()- Simple style with reset background
Menu Bar
use ;
// Create menus with items, separators, shortcuts, and submenus
let menus = vec!;
// Create state
let mut state = new;
state.focused = true;
// Render the menu bar
let menu_bar = new
.style;
let = menu_bar.render_stateful;
// Handle keyboard (arrows navigate, Enter selects, Esc closes)
if let Some = handle_menu_bar_key
// Handle mouse (click to open, hover to switch menus)
handle_menu_bar_mouse;
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 item
Style presets:
MenuBarStyle::default()- Dark themeMenuBarStyle::light()- Light themeMenuBarStyle::minimal()- Simple style with reset background
Mouse Pointer
use ;
// Create state (disabled by default)
let mut state = default;
// Enable and update position from mouse events
state.set_enabled;
state.update_position;
// Create pointer with custom style
let pointer = new
.style;
// Render LAST to appear on top of other widgets
pointer.render;
// 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-defined
Custom styling:
let style = default
.symbol
.fg // Orange
.bg;
List Picker
use ;
use Line;
let items = vec!;
let mut state = new;
// Navigate
state.select_next;
state.select_prev;
// Custom rendering
let picker = new
.title
.render_item;
Tree View
use ;
let nodes = vec!;
let mut state = new;
state.toggle_collapsed; // Collapse/expand
let tree = new
.render_item;
Accordion
use ;
// Single mode: only one section expanded at a time (FAQ-style)
let mut state = new
.with_mode;
// Multiple mode: any number can be expanded (settings-style)
let mut state = new
.with_mode
.with_expanded;
// Toggle, expand, collapse
state.toggle;
state.expand;
state.collapse;
// Create accordion with custom renderers
let accordion = new
.id_fn
.render_header
.render_content;
Breadcrumb
use ;
// Create breadcrumb items with optional icons
let items = vec!;
// Create state
let mut state = new;
state.focused = true;
// Create breadcrumb with default style (uses " > " separator)
let breadcrumb = new;
let click_regions = breadcrumb.render_stateful;
// Different style presets:
// - BreadcrumbStyle::slash() - " / " (Unix path style)
// - BreadcrumbStyle::chevron() - " › " (Unicode chevron)
// - BreadcrumbStyle::arrow() - " → " (Unicode arrow)
// - BreadcrumbStyle::minimal() - Subdued colors
let breadcrumb = new
.style;
// Handle keyboard (arrows navigate, Enter activates, e expands ellipsis)
if let Some = handle_breadcrumb_key
// Handle mouse clicks
handle_breadcrumb_mouse;
// Dynamic path manipulation
state.push;
state.pop;
state.clear;
Ellipsis collapsing: Long paths automatically collapse with ... (configurable threshold).
Example: Home > ... > Settings > Profile when showing 7+ items.
Tab View
use ;
use ClickRegionRegistry;
// Create tabs with optional icons and badges
let tabs = vec!;
// Create state
let mut state = new;
// Create style (tabs on left side)
let style = left.tab_width;
// Create tab view with content renderer
let tab_view = new
.style
.content;
// Render and register click regions
let mut registry: = new;
tab_view.render_with_registry;
// Handle keyboard (arrows navigate, Enter focuses content, Esc focuses tabs, 1-9 direct select)
handle_tab_view_key;
// Handle mouse clicks
handle_tab_view_mouse;
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 dividers
Split Pane
use ;
use ClickRegionRegistry;
// Create state with initial split percentage (50% = equal split)
let mut state = new;
state.divider_focused = true; // Enable keyboard resize
// Create split pane with horizontal orientation (left | right)
let split_pane = new
.orientation
.style
.min_percent // Minimum 10% for first pane
.max_percent; // Maximum 90% for first pane
// Calculate areas for manual rendering
let = split_pane.calculate_areas;
// Register click regions for mouse support
let mut registry: = new;
registry.register;
registry.register;
registry.register;
// Or use the all-in-one render method with closures
split_pane.render_with_content;
// Handle keyboard (arrows resize when divider focused, Home/End for min/max)
handle_split_pane_key;
// Handle mouse (drag divider to resize)
handle_split_pane_mouse;
Orientations:
Orientation::Horizontal- Left | Right split (default)Orientation::Vertical- Top / Bottom split
Style presets:
SplitPaneStyle::default()- Dark gray divider with grab indicatorSplitPaneStyle::minimal()- Thin line divider, no backgroundSplitPaneStyle::prominent()- Blue divider with high visibility
Log Viewer
use ;
let logs = vec!;
let mut state = new;
// Search
state.search;
state.next_match;
// Scroll
state.scroll_down;
state.scroll_right;
let viewer = new
.title
.show_line_numbers;
Diff Viewer
use ;
// 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 = from_unified_diff;
// 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 = new
.title
.show_stats; // 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/kor↑/↓: Scroll up/downh/lor←/→: Scroll left/rightg/GorHome/End: Go to top/bottom]/[: Next/previous hunkn/N: Next/previous change (or search match)vorm: Toggle view mode (unified/side-by-side)/: Start searchPgUp/PgDnorCtrl+U/D: Page navigation
Style presets:
DiffViewerStyle::default()- Green additions, red deletions with dark backgroundsDiffViewerStyle::high_contrast()- Brighter colors for better visibilityDiffViewerStyle::monochrome()- Bold/dim text without colors
Step Display
use ;
let steps = vec!;
let mut state = new;
// Update progress
state.start_step;
state.start_sub_step;
state.complete_sub_step;
state.add_output;
state.complete_step;
let display = new;
File Explorer
use ;
use PathBuf;
let mut state = new;
// 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; // Filter by 'r'
let explorer = new
.title
.show_hidden;
Toast Notifications
Toast notifications provide transient feedback to users:
use ;
// Show a toast for 3 seconds
app.toast_state.show;
// In your render function:
// 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"
Mouse Click Handling for Buttons
Buttons support mouse clicks through click regions. Use render_with_registry() for the simplest pattern:
use ;
use ClickRegionRegistry;
// In your render function:
// In your event handler:
For more control, use the two-step pattern with render_stateful():
let region = button.render_stateful;
registry.register;
Examples
Run the examples to see components in action:
# Interactive Components
# Display & Viewer Components
# Navigation & Layout Components
# Combined Demo (requires filesystem feature)
Comparison with Alternatives
| 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 | ❌ | ❌ |
License
MIT