lv-tui
A reactive TUI framework for Rust. Component tree, reactive state, CSS-like styling, event bubbling, focus management — everything you need to build complex terminal applications.
use lv_tui::prelude::*;
use lv_tui::Component;
#[derive(Component)]
struct Counter {
#[reactive(paint, copy)]
count: i32,
}
impl Counter {
fn new() -> Self { Self { count: 0 } }
fn render(&self, cx: &mut RenderCx) {
cx.line(format!("count: {}", self.get_count()));
cx.line("press + to increment, q to quit");
}
fn event(&mut self, event: &Event, cx: &mut EventCx) {
if event.is_key(Key::Char('+')) { self.set_count(self.get_count() + 1, cx); }
if event.is_key(Key::Char('q')) { cx.quit(); }
}
}
fn main() -> lv_tui::Result<()> {
App::new(Counter::new()).run()
}
Features
- Component tree — compose UIs with Column, Row, Stack, Block, Scroll, Overlay
- Reactive state —
#[reactive(paint)] auto-triggers repaint on change
- Event bubbling — Capture → Target → Bubble with
stop_propagation()
- Focus management — Tab/Shift+Tab navigation with Focus/Blur events
- CSS-like stylesheets — type/class/id selectors with style inheritance
- Unicode — CJK/Emoji wide characters, text wrap, truncation, alignment
- Async tasks —
cx.spawn() for background work with TaskComplete events
- Debug view — press
d to visualize component borders and labels
Widgets
| Widget |
Description |
Label |
Text display with styling |
Input |
Single-line text input with cursor |
Column |
Vertical layout container |
Row |
Horizontal layout container |
Stack |
Layered/z-order container |
Block |
Border + padding wrapper with optional title |
Scroll |
Scrollable content container |
Overlay |
Modal dialog with background dimming |
Table |
Data table with headers, selection, and sorting |
Tabs |
Tabbed container with keyboard navigation |
Select |
Dropdown with keyboard navigation |
Checkbox |
Toggleable boolean with label |
RadioGroup |
Mutually exclusive radio selection |
ProgressBar |
Unicode 8-segment progress bar |
Dialog |
Border + key bindings (Esc/Enter) wrapper |
SplitPane |
Resizable split panels (Ctrl+arrows) |
VirtualList |
Virtual-scrolling list for large datasets |
Getting Started
Quick example
Create a settings form with checkboxes, radio group, and keyboard navigation:
use lv_tui::prelude::*;
use lv_tui::widgets::*;
struct App { content: Column }
impl App {
fn new() -> Self {
Self {
content: Column::new()
.child(Label::new("Settings").style(Style::default().bold()))
.child(Label::new(""))
.child(Checkbox::new("Enable notifications"))
.child(Checkbox::new("Auto-save").checked())
.child(Label::new(""))
.child(Label::new("Theme:"))
.child(RadioGroup::new(vec![
"Light".into(), "Dark".into(), "System".into(),
]))
.child(Label::new(""))
.child(Label::new("Tab: switch Space: toggle q: quit")
.style(Style::default().fg(Color::Gray))),
}
}
}
impl Component for App {
fn render(&self, cx: &mut RenderCx) { self.content.render(cx); }
fn event(&mut self, e: &Event, cx: &mut EventCx) {
if e.is_key(Key::Char('q')) { cx.quit(); return; }
self.content.event(e, cx);
}
fn layout(&mut self, r: lv_tui::geom::Rect, cx: &mut lv_tui::component::LayoutCx) { self.content.layout(r, cx); }
fn measure(&self, c: lv_tui::layout::Constraint, _cx: &mut lv_tui::component::MeasureCx) -> lv_tui::geom::Size {
lv_tui::geom::Size { width: c.max.width, height: c.max.height }
}
fn focusable(&self) -> bool { false }
}
fn main() -> lv_tui::Result<()> {
lv_tui::app::App::new(App::new()).run()
}
Theme support
use lv_tui::prelude::*;
fn main() -> lv_tui::Result<()> {
set_theme(Theme::Light); }
Clipboard
fn event(&mut self, event: &Event, cx: &mut EventCx) {
cx.copy_to_clipboard("copied text");
}
[dependencies]
lv-tui = "0.1"
cargo run --example counter
cargo run --example demo_app
License
MIT