photon-ui
A blazing fast, minimal terminal UI framework for Rust.
Photon UI is a lightweight, high-performance TUI library built on crossterm. It features a custom differential renderer that only redraws what changed, ANSI-aware text wrapping, OSC 8 hyperlink support, terminal image rendering (Kitty and iTerm2 protocols), a Cassowary constraint-based layout engine, and a component-based architecture.
Features
- Differential renderer — Only updates cells that changed between frames, minimizing terminal output and improving performance.
- Component-based architecture — Build UIs by composing simple, reusable
Componenttrait implementations. - Built-in components — 27 components including text, input, editor, buttons, tables, trees, sidebars, tabs, modals, panels, progress bars, markdown, and image widgets.
- Constraint-based layout — Split terminal space using the Cassowary solver via
Layout,Constraint, andRect. - Dual editing modes — Both Emacs and vim keybindings for
InputandEditorcomponents, switchable at runtime. - Terminal image support — Render images inline using the Kitty graphics protocol or iTerm2 inline images.
- ANSI-aware text handling — Properly measures and wraps text containing ANSI escape sequences, Unicode, and grapheme clusters.
- Overlays and modals — Position floating content with
Anchor-based constraints and modal dialogs that capture input. - Focus management — The
TUIruntime handles focus cycling and routes input events to the focused component. - Zero unnecessary dependencies — Core functionality relies only on
crossterm,unicode-width,unicode-segmentation,pulldown-cmark,base64,thiserror, andkasuari.
Quick start
Add photon-ui to your Cargo.toml:
[]
= "0.1"
Build a simple app:
use ;
use Text;
let mut tui = TUInew;
tui.mount;
tui.render_frame.unwrap;
For a real application, use ProcessTerminal to drive the actual terminal:
use ;
use ;
use ;
use Duration;
Components
| Component | Description |
|---|---|
Text |
Static text with optional horizontal and vertical padding. |
TruncatedText |
Text that truncates with an ellipsis when wider than the container. |
Box |
A container with configurable vertical padding and optional background styling. |
Spacer |
Empty vertical space. |
Input |
Single-line text input with Emacs and vim modes, kill-ring, and undo. |
Editor |
Multi-line text editor with Emacs and vim modes, kill-ring, and undo. |
SelectList |
Scrollable list with keyboard navigation and selection. |
SettingsList |
Toggle list for boolean settings. |
Loader |
Animated spinner with optional colored message. |
CancellableLoader |
Spinner that can be cancelled via Ctrl+C. |
Markdown |
Renders CommonMark markdown (headings, bold, italic, inline code, lists). |
ImageWidget |
Displays images inline via Kitty or iTerm2 graphics protocols. |
Button |
Clickable button with primary, ghost, cream, text, and dark variants. |
Breadcrumbs |
Hierarchical breadcrumb navigation trail. |
Container |
Generic container for composing child components. |
Div |
Layout container that splits space among children with borders and padding. |
Divider |
Horizontal or vertical divider line, optionally with a label. |
Header |
Page header with title and right-aligned actions. |
Modal |
Modal dialog overlay with title, border, and focus capture. |
Panel |
Panel container with optional rounded, thin, thick, or double borders. |
ProgressBar |
Progress bar with optional label and percentage display. |
Sidebar |
Sidebar navigation with icons and keyboard selection. |
StatusBar |
Status bar with left, center, and right segments. |
Table |
Table with sortable columns, row selection, and keyboard navigation. |
Tabs |
Tab bar with keyboard navigation between tabs. |
TreeView |
Collapsible tree view with expand/collapse and keyboard navigation. |
Architecture
Component trait
Every visible element implements the Component trait:
pub trait Component {
fn render(&self, width: u16) -> Result<Rendered, RenderError>;
fn render_rect(&self, rect: Rect) -> Result<Rendered, RenderError>;
fn handle_input(&mut self, event: &Event) -> InputResult;
fn wants_key_release(&self) -> bool;
fn as_focusable(&self) -> Option<&dyn Focusable>;
fn as_focusable_mut(&mut self) -> Option<&mut dyn Focusable>;
}
renderproduces the lines of text (and optional cursor or image commands) for the current frame.render_rectrenders into a specific rectangular area, used by layout-aware components.handle_inputreceives events when the component has focus.Focusablecomponents participate in the tab order managed byTUI.
Renderer
The Renderer tracks the previous frame and computes a minimal set of cursor movements and writes needed to update the terminal. This avoids clearing the screen or redrawing unchanged content. It automatically selects one of three strategies:
- FirstRender — outputs all lines (assumes a clean alternate screen).
- FullRedraw — clears the screen and redraws everything (used on resize).
- Diff — computes the first and last changed line and only rewrites that region.
Layout engine
The layout system uses the kasuari Cassowary constraint solver. A Layout splits a Rect into sub-rects based on Constraint values (Length, Min, Max, Percentage, Ratio, Fill) with configurable Direction, Flex, Margin, and Spacing.
use ;
use Layout;
let layout = vertical;
let areas = layout.split;
TUI runtime
TUI owns the terminal backend, manages a stack of components, handles focus cycling (Tab / Shift+Tab), and supports overlays positioned with Anchor and OverlayConstraints. Modal dialogs capture all input until dismissed with Esc.
Input modes
Both Input and Editor support Emacs and vim editing styles:
| Emacs | Vim | Action |
|---|---|---|
Ctrl+A |
0 or ^ |
Move to start of line |
Ctrl+E |
$ |
Move to end of line |
Ctrl+K |
D |
Kill to end of line |
Ctrl+Y |
p |
Yank (paste) |
Ctrl+W |
daw |
Kill word |
Ctrl+F / Ctrl+B |
l / h |
Move forward / backward |
| — | i / Esc |
Enter / exit insert mode |
Enable vim mode at construction time or toggle it at runtime with set_vim_mode_enabled.
Running the demo
The included demo showcases every component across multiple pages:
Controls:
| Key | Action |
|---|---|
1–5 |
Switch demo page |
Tab / Shift+Tab |
Cycle focus |
q or Ctrl+C |
Quit |