Rustact is a React-inspired, async terminal UI framework built on top of ratatui, crossterm, and tokio. Components render into a virtual tree, hooks manage state, side effects, and context, and the runtime diff-patches frames to keep redraws quick even when ticks fire continuously.
Install from crates.io:
cargo add rustactuse rustact::{component, App, Element, Scope};
use rustact::{is_button_click, ButtonNode};
fn counter(ctx: &mut Scope) -> Element {
let (count, set_count) = ctx.use_state(|| 0i32);
ctx.use_effect((), move |dispatcher| {
let mut events = dispatcher.events().subscribe();
let handle = tokio::spawn(async move {
while let Ok(event) = events.recv().await {
if is_button_click(&event, "counter:inc") {
set_count.update(|value| *value += 1);
}
}
});
Some(Box::new(move || handle.abort()))
});
Element::vstack(vec![
Element::text(format!("Count: {count}")),
Element::button(ButtonNode::new("counter:inc", "+").filled(true)),
])
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let root = component("Counter", counter);
App::new("counter-demo", root)
.watch_stylesheet("styles/app.css")
.run()
.await
}cd examples/rustact-demo
RUSTACT_WATCH_STYLES=1 cargo run- Counter, gauges, tables, trees, forms, toasts, and tips live in one app under
examples/rustact-demo/src/main.rs. - Set
RUSTACT_WATCH_STYLESto1,true, oronto hot-reloadstyles/demo.csswhile the example is running.
cd examples/ops-dashboard
cargo runExplore layered overlays (LayeredNode), toasts (ToastStackNode), tabs, and modal dialogs, all powered by hooks and shared context.
cargo install cargo-generate
cargo generate \
--git https://github.com/IllusiveBagel/rustact \
--branch main \
--path templates/rustact-app \
--name my-rustact-app
cd my-rustact-app
cargo runThe template mirrors the demo’s structure (components, dispatcher usage, stylesheet, README) so you can immediately iterate on your own TUI.
- Component + hook model – Declare components with
component("Name", handler)and manage state viause_state,use_reducer,use_ref,use_memo,use_callback,use_effect,provide_context,use_context, and the dedicateduse_text_input/use_text_input_validationhooks. - Async runtime + event bus –
tokiodrives terminal IO, ticks, shutdown, and external signals; subscribe toFrameworkEvents throughDispatcher::events()for keyboard, mouse, resize, and timer events. - Injectable drivers & headless mode – Use
App::with_driverto plug deterministic drivers for tests or simulations, or callApp::headless()to render without a terminal for snapshots. - Rich widget set –
Elementbuilders cover flex layouts, blocks, lists, tables, trees, forms, gauges, buttons, inputs, tabs, layered overlays, modals, and toast stacks. - Text input system – Handles focus rings, cursor placement, secure mode, validation state, and shared registries so inputs behave like native controls (including mouse hits and Tab cycling).
- CSS-inspired styling – The
Stylesheetparser understands:root, element/id/class selectors, and custom properties so you can recolor UI, resize columns, rename labels, or toggle fills without recompiling. - Hot-reloadable themes –
App::watch_stylesheetplus theRUSTACT_WATCH_STYLESenv var reload styles wheneverstyles/demo.css(or any configured path) changes. - View diffing + tracing – Frames are diffed before drawing, and every render/event/shutdown emits
tracingspans so you can profile and debug behavior.
Rustact looks for a sibling stylesheet (for the demos that is styles/demo.css). You can define palette tokens in :root, override widget-specific properties, and reload on every save.
:root {
--accent-color: #5af78e;
--warning-color: #ffb86c;
}
button#counter-plus {
accent-color: var(--accent-color);
--filled: true;
}
input.feedback-name {
border-color: #8be9fd;
placeholder: "Display name";
}Run any example with RUSTACT_WATCH_STYLES=1 cargo run to live-reload the stylesheet. For custom apps, chain .watch_stylesheet("styles/app.css") on App and keep the env var enabled while iterating.
Read website/content/docs/styling.md for the full selector/property reference and integration tips.
- Docs site – https://illusivebagel.github.io/rustact (root of every published guide).
- Developer guide – https://illusivebagel.github.io/rustact/docs/guide/ (high-level concepts and workflows).
- Template quickstart & tutorial – https://illusivebagel.github.io/rustact/docs/tutorial/ (generate a project and build a mini app step-by-step).
- Architecture deep dive – https://illusivebagel.github.io/rustact/docs/architecture/ (runtime, hooks, renderer, events).
- Styling reference – https://illusivebagel.github.io/rustact/docs/styling/ (selectors, properties, runtime integration).
- Widget catalogue – https://illusivebagel.github.io/rustact/docs/widgets/ (all built-in widgets, code, and screenshot guidance).
- API publishing workflow – https://illusivebagel.github.io/rustact/docs/api-docs/ (how GitHub Pages bundles
cargo doc). - Roadmap – https://illusivebagel.github.io/rustact/docs/roadmap/ (prioritized features and milestones).
- examples/README.md – describes each example crate and how to run it.
- templates/rustact-app/ – starter project used by
cargo generate.
src/– core runtime, renderer, hooks, context, styles, text input system, and integration tests.examples/– standalone apps (rustact-demo,ops-dashboard) that depend on the local crate via path dependencies.templates/– reusable scaffolds; currentlytemplates/rustact-app.website/– documentation source consumed by GitHub Pages.CHANGELOG.md,RELEASE.md,MAINTAINERS.md,CONTRIBUTING.md– project process, release, and ownership docs.
- Follow the Code of Conduct (Contributor Covenant).
- Read CONTRIBUTING.md for tooling, test, and review expectations.
- Maintainer responsibilities live in MAINTAINERS.md; release steps in RELEASE.md; history in CHANGELOG.md.
Rustact is distributed under the MIT License. By contributing, you agree that your contributions will be licensed under the same terms.