Rustact
Rustact is an experimental, React-inspired framework for building async terminal UIs in Rust. It layers a component/hook system on top of ratatui, offering familiar primitives such as components, state, effects, context, and an event bus.
Community & contributions
- Read the Contributing guide for setup, workflows, and coding standards.
- Review the Code of Conduct; we follow the Contributor Covenant.
- Maintainer contacts and responsibilities live in MAINTAINERS.md.
- Release duties and checklists are captured in RELEASE.md.
- New issues/PRs should use the provided GitHub templates for consistent triage.
Features
- Component tree & hooks – Define components via
component("Name", |ctx| ...)and manage state withuse_state,use_reducer,use_ref,use_effect,use_memo,use_callback, andprovide_context/use_context. - Async runtime – Built on
tokio, handling terminal IO, ticks, and effect scheduling without blocking the UI thread. - Injectable runtime drivers – Swap the event/tick/shutdown drivers with
App::with_driver(...)for deterministic tests or custom IO sources. - Structured tracing – The runtime emits
tracingspans/logs around renders, events, and shutdown, so you can inspect behavior with anytracingsubscriber. - Event bus – Subscribe to keyboard/mouse/resize/tick events via
ctx.dispatcher().events().subscribe()and react within hooks or spawned tasks. - Ratatui renderer – Converts the virtual tree into
ratatuiwidgets, handling layout primitives like blocks, vertical/horizontal stacks, styled text, list panels, gauges, tables, tree views, forms, and rich text inputs. - View diffing – The runtime caches the previous view tree and skips terminal draws when nothing has changed, keeping renders snappy even with frequent ticks.
- Mouse interactions – Define button or input nodes with stable IDs; the renderer automatically maps mouse hitboxes so event handlers can react to clicks and focus changes.
- Text inputs & validation hooks –
use_text_inputbinds component state to editable fields (with cursor management, tab focus, and secure mode), whileuse_text_input_validationorhandle.set_status(...)drive contextual border colors and helper text. - Tabs, overlays, and toasts – Compose
TabsNode,ModalNode,LayeredNode, andToastStackNodeto build multi-pane dashboards with modal dialogs and notification stacks without wiring bespoke renderer code. - CSS-inspired styling – Drop tweaks into
styles/demo.cssto recolor widgets, toggle button fills, rename gauge labels, change list highlight colors, resize form/table columns, or theme inputs without touching Rust code. - Optional style hot reload – Set
RUSTACT_WATCH_STYLES=1(ortrue/on) to have the runtime pollstyles/demo.cssand live-reload the stylesheet without restarting the app. - Demo app –
src/main.rsnow composes every hook (state, reducer, ref, memo, callback, effect, context) with all widgets (text, flex, gauge, buttons, lists, tables, trees, forms) so you can see each feature in one place.
Documentation
docs/guide.md– day-to-day developer guide (setup, workflows, hook primer).docs/tutorial.md– step-by-step tutorial for building a fresh Rustact app.docs/architecture.md– deep dive into the runtime, hooks, renderer, and events.docs/styling.md– CSS subset reference and theming tips.docs/roadmap.md– prioritized initiatives to steer ongoing work.docs/api-docs.md– publishing instructions for hostingcargo docoutput.docs/template.md– outline for the upcomingcargo generatestarter template.templates/rustact-app/– ready-to-use project scaffold consumable viacargo generate.
Starter template
Spin up a fresh app via cargo-generate:
The template mirrors the demo’s patterns (hooks, dispatcher events, text inputs) plus a default stylesheet and README.
API docs hosting
The workflow .github/workflows/publish-docs.yml builds cargo doc --no-deps on every push to main and deploys the result via GitHub Pages. Enable Pages → "GitHub Actions" in repo settings to activate it. Manual steps and customization tips live in docs/api-docs.md.
Tracing logs
Rustact emits tracing spans throughout the runtime. Enable them in your binary (or the demo) by adding a subscriber:
use EnvFilter;
Then set RUST_LOG=info (or more specific filters) before running the app to see render requests, external events, and shutdown diagnostics.
Running the demo
While running:
- Press
+,-, orr, or click the on-screen-/+buttons to interact with the counter (watch the progress gauge update as the count approaches ±10). - Observe the stats panel and event log streaming the five most recent framework events.
- Tab between the name/email/token inputs, click to focus, and type to see live validation statuses plus CSS-driven border and background colors.
- Check the framework overview banner and tips column to see keyed components, fragments, and shared context in action.
- Exit with
Ctrl+C.
Watching styles without restarting
RUSTACT_WATCH_STYLES=1
When the env var is set (accepts 1, true, or on) and styles/demo.css exists next to the binary, Rustact will poll the file, re-parse it on change, and trigger a render so you can see your CSS edits immediately. The demo and ops dashboard both honor the flag; when the file is missing, the runtime logs a warning and keeps using the embedded stylesheet.
Ops dashboard showcase
While running:
- Press
1/2to switch between the overview and streaming logs tabs. - Press
ito pop open the incident modal,Escto close it. - Let the app run to watch deployment toasts bubble up; press
cto dismiss the oldest toast. - All overlays are composed via the new
LayeredNode,ModalNode, andToastStackNode, so they are reusable in your own apps.
Styling via CSS
Rustact loads styles/demo.css on startup. The CSS parser understands simple selectors (element, element#id, element.class) plus custom properties, so you can retheme the terminal UI without recompiling:
:rootdefines palette tokens such as--accent-color,--warning-color, and friends that the demo injects into itsThemecontext.button#counter-plus(and#counter-minus) setaccent-colorand--filledto control button appearance.gauge#counter-progresscan override the bar color and--labeltext.list#statsexposes--highlight-colorand--max-items, whiletable#servicesreads--column-widthsandform#releasereads--label-width.input,input#feedback-name, etc. configure accent/border/text/background colors, whiletip.keyboard/.mouse/.contextuse the standardcolorproperty to tint each info card.
Save the file and rerun cargo run to see your tweaks reflected immediately.
See docs/styling.md for a deeper dive into selectors, property types, and integration tips.
Creating components
use ;
use Color;
let app = new;
Each render receives a Scope, giving access to hooks, the dispatcher, and context. Components can compose other components with .into():
vstack
Advanced widgets
Tables, trees, and forms ship with builder-style APIs so you can fluently describe structured layouts:
use ;
use Color;
let table = new
.header
.widths
.highlight;
table;
TreeNode/TreeItemNode let you model recursive hierarchies, while FormNode + FormFieldNode render labeled key/value pairs with validation state highlighting.
Text inputs & validation
use ;
use FormFieldStatus;
use_text_input registers a focusable binding with the shared registry. The runtime tracks hitboxes, so clicking anywhere in the input focuses it, while Tab/Shift+Tab move between inputs in declaration order. A blinking cursor shows when the field is focused, and .secure(true) masks sensitive values. Use ctx.use_text_input_validation or handle.set_status(FormFieldStatus::Error) to tint the field, then read helper text from the same status to explain errors or warnings.
Reducer & ref hooks
use ;
use_reducer returns the current value plus a dispatch handle that batches state updates through your reducer; use_ref stores mutable data without triggering re-renders (handy for metrics or imperative handles).
View diffing
Every render builds a View tree, compares it with the previous frame, and only asks the renderer to draw when something actually changed. Hooks, effects, and state updates still run, but redundant terminal flushes are avoided—mirroring the virtual DOM approach.
Next steps
- Package reusable components and publish to crates.io.
License
Rustact is distributed under the MIT License. By contributing, you agree that your contributions will be licensed under the same terms.