▌ntui

▌ ntui

An Ink-style TUI library, for Rust.

Build fullscreen (or inline) terminal UIs out of components and hooks, with a React-style retained fiber tree, flexbox layout via taffy, and minimal-diff terminal output via crossterm.

use ntui::{component, element, render, BorderStyle, Color, FlexDirection, KeyCode, Weight};

#[component]
fn Counter(hooks: &mut ntui::Hooks) -> ntui::Element {
    let count = hooks.use_state(|| 0i32);
    let app = hooks.use_app();
    let c = count.clone();
    hooks.use_input(move |ev, _| match ev.code {
        KeyCode::Up => c.update(|n| *n += 1),
        KeyCode::Down => c.update(|n| *n -= 1),
        KeyCode::Char('q') => app.exit(),
        _ => {}
    });
    element! {
        View(flex_direction: FlexDirection::Column, padding: 1, border_style: BorderStyle::Round) {
            Text(content: format!("count: {}", count.get()), weight: Weight::Bold)
            Text(content: "↑/↓ to change · q to quit", color: Color::DarkGrey)
        }
    }
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), ntui::Error> {
    render(element!(Counter)).await
}

Run it: cargo run --example counter

Features

Components + hooks

#[component] functions and use_state / use_effect / use_input / use_future / use_stream / use_context / use_scroll / use_scrollback / use_app — React’s hook rules, in Rust.

element!

A JSX-like macro for View/Text trees with typed props, keyed children, and #(...) for iterators and fragments.

A real reconciler

A retained, keyed fiber tree with props_eq short-circuiting for unchanged subtrees, and a bounded re-render loop for cascading updates.

Flexbox layout

Powered by taffy — flex_direction, gap, padding, margin, justify_content, align_items, flex_grow, percentage/cell dimensions, borders.

Cell-diff painting

Every frame paints into an in-memory buffer; only the cells that changed are written to the terminal.

Fullscreen or inline

render() for a classic alternate-screen app, or render_inline() to commit finished output into the terminal’s real scrollback.

Frequently asked questions

Is ntui published on crates.io?
Yes — add `ntui = "0.1"` to your Cargo.toml dependencies.
What Rust edition does ntui require?
The 2024 edition, on a recent stable toolchain.
Does ntui support mouse input?
Not in v1 — only keyboard input is routed to components.
What license is ntui under?
Dual-licensed under MIT or Apache-2.0, at your option.
How is ntui different from a widget-library TUI framework?
ntui is components and hooks (use_state, use_effect, use_input, use_future, ...) over a real React-style reconciler and flexbox layout. A first-party ntui::widgets module (buttons, selects, tables, modals, ...) ships on top, built from the same five primitives — an optional layer, not the programming model.
Can ntui render inline instead of fullscreen?
Yes — render_inline() commits finished output into the terminal’s real scrollback via use_scrollback, while a live region redraws at the bottom.