Skip to content
UrushiDocumentation

TUI overview

urushi-tui-app is the starting point for a new full-screen application. You describe the application’s state, the events it accepts, how those events change state, and how the current state looks. The runtime owns the event loop, drawing, and terminal session.

This model is The Elm Architecture (TEA). The name identifies a small set of responsibilities; you do not need to know Elm or TEA before using Urushi. The first-application tutorial introduces every part while building a complete keyboard-driven program.

Use it for applications whose interface remains active: dashboards, browsers, inspectors, monitors, editors, and other screens where input changes a model and the current model is redrawn.

The counter below receives two key events. Each key immediately updates the Model, while slower background work returns later as Effect-completion Messages. Quitting restores the terminal and returns the final Model:

Cyan and yellow are emitted by the real View renderer. The gap between key updates and effect completions is the application loop at work, not a prerecorded text animation.

A TEA application is a loop with one source of truth:

  1. The Model contains everything that can change the application’s behavior or appearance.
  2. A Message describes one event, such as a key press, timer tick, or file load result.
  3. update handles that message and changes the model.
  4. view reads the model and returns the next Urushi View.
  5. The runtime draws that View and waits for the next message.

update is the only place that mutates the model. view does not perform work or write to the terminal; it only describes what the current model should look like. One-shot work is returned as an Effect, and ongoing event sources are declared as Subscriptions. Their results re-enter the same loop as messages.

This means a key does not modify a widget directly. It becomes a message, the message changes the model, and the model produces a new View. That one-way path is what keeps application behavior testable without a live terminal.

The Urushi application loop

Boxes are values. Labels on arrows are functions or runtime actions. The runtime draws each View, then delivers the next Message.

The Urushi TEA application loopIn a closed loop, a message enters update, update changes the model, view turns the model into a View, and the runtime draws it. Input, subscription events, and effect completions become the next message.YOUR APPLICATIONMessagedata: what happenedModeldata: current stateViewdata: next frameupdateviewUrushi runtime + terminaldraw View · wait for the next eventdrawnext MessageMessages come from input, subscription events, and effect completions.The Urushi TEA application loopIn a closed loop, a message enters update, update changes the model, view turns the model into a View, and the runtime draws it. Runtime events become the next message.YOUR APPLICATIONMessagedata: what happenedModeldata: current stateViewdata: next frameupdateviewRuntime + terminaldraw · wait for an eventdrawnext Messageinput · subscriptions · effect completions

In the recording, key updates belongs to the Model. The runtime turns each keypress into a Message, calls update, calls view with the changed Model, and draws the returned View. The delayed job does not mutate the Model from a worker thread; its result returns through the same Message entrance. That is TEA in Urushi.

Build your first application without prior TEA knowledge →

Starting point Choose You own Urushi owns
Build a new full-screen application urushi-tui-app Domain model, messages, update rules, and View Input sources, effects, ordered delivery, drawing, and terminal restoration
Drive a synchronous terminal loop yourself urushi-tui Session, events, resize decisions, timing, and shutdown Screen buffers, cell diffing, transactional commit, and failed-draw recovery
Add Views to an existing Ratatui application urushi-adapter-ratatui Ratatui terminal, event loop, state, scheduling, and graphics lifecycle Stateless View resolution and writes into the supplied Ratatui buffer

These are ownership choices, not progressively more advanced layers. Start with the row matching the loop the application already has. The recording and first-application tutorial use the first row.

  • A testable application model. init, update, view, and subscriptions separate state transitions from terminal I/O. The application model contains no Ratatui backend types.
  • Terminal and external events as messages. Applications can subscribe to keys, paste, focus, mouse input, surface changes, intervals, process signals, terminal draw errors, and application-defined streams or producers.
  • Work outside update. Effects cover blocking work, futures, delayed messages, latest-only replaceable work, concurrent batches, and orderly shutdown. Every completion returns through the message path.
  • Full-screen presentation. view returns the same styled, layout-aware Urushi View used by CLI output. The runtime resolves it against the current surface and writes a frame of terminal cells.
  • Runtime ownership. The runtime orders accepted messages, reconciles subscriptions, coalesces physical draws without dropping model transitions, and restores the terminal session on normal and error returns. During panic unwinding its session guard makes a final best-effort restoration attempt.
  • A production entry point. urushi_tui_app::run(app) supplies the default executor, clock, terminal connection, presentation, and session profile. Runtime::new(app) makes the physical backend, executor, clock, and session options configurable; presentation remains the concrete urushi-tui screen.

Focus movement, modal stacks, key maps, commands, and navigation rules remain ordinary application state and messages. Urushi provides the application and presentation machinery; it does not prescribe those product semantics.

TEA application loop and native screen boundaryInput, subscription events, and effect completions become messages. Update changes the model and can return effects. View projects the model into an Urushi View, which the renderer writes through the native Screen and Frame.EVENT SOURCESSubscriptionsinput · surface · timerssignals · custom streamsEffect resultswork · future · delayMessageordered deliveryupdate&mut ModelModelapplication stateEffectruntime executesview(&Model)urushi::ViewURUSHI-TUI PRESENTATIONScreen → Frame → CommandWriter

The View is the same renderer-neutral tree used by the rest of Urushi. A full-screen application does not need a second layout language, and its model does not contain Ratatui or another backend’s types.

Use Layout for ordinary screen structure, components for lists, tables, trees, and scrollbars, and Canvas for positioned or overlapping drawing. To place Kitty or Sixel images in those layouts, add terminal graphics and let the runtime own their frame lifecycle.

A view method can use core layout and theme values directly:

use urushi::{BlockStyle, Border, TextStyle, View};
fn view(model: &Model) -> View {
View::block(
BlockStyle::new()
.border(Border::ROUNDED)
.padding((0, 1)),
View::text(model.status.clone(), TextStyle::new()),
)
}

For status == "Ready", that region resolves to:

Rendered frame region
╭───────╮
│ Ready │
╰───────╯

An Application provides four operations:

  • init creates the initial model and effect;
  • update applies one message and returns the next effect;
  • view projects the current model into an Urushi View; and
  • subscriptions declares the event sources active for the current model.

Effects describe work outside the model. Subscriptions describe ongoing input, surface, timer, signal, or application-defined sources. Their results return as messages and pass through the same ordered update path.

Build your first application →

Choose effects, subscriptions, and backpressure →

Understand message ordering and draw scheduling →

urushi-tui exposes a concrete Screen and a Frame borrowed for one draw. The screen owns working and committed Urushi cell buffers, computes their diff, writes terminal commands, and adopts the new frame only after every output step succeeds. A caller can drive it synchronously without the application runtime.

Application remains written against Urushi types, not a presentation backend. The runtime resolves its View and writes the resulting graphemes through the same Frame API.

Build a caller-owned loop with Screen and Frame →

urushi-adapter-ratatui supplies ViewWidget and related adapters for an application that already owns a Ratatui loop and buffer. Neither urushi-tui nor urushi-tui-app depends on Ratatui.

Use Urushi inside an existing Ratatui application →

The three independently useful full-screen surfaces have separate package boundaries:

TUI crate boundariesThe TEA application runtime depends on the Urushi-owned screen engine. The separate Ratatui adapter translates core Urushi views into a buffer owned by an existing Ratatui application.urushiTheme · View · layouturushi-tuiScreen · Frame · buffersurushi-tui-appApplication · Runtime · effectsurushi-adapter-ratatuicaller-owned BufferRatatui applicationloop · terminal · frameCRATE LAYERS
  • urushi-tui owns a synchronous Screen, draw-scoped Frame, cell buffers, diffing, and transactional output;
  • urushi-tui-app owns Application, Runtime, effects, subscriptions, delivery, scheduling, input, and session ownership; and
  • urushi-adapter-ratatui owns stateless conversion into a caller-owned Ratatui buffer.

The low-level frame engine is a concrete Urushi Screen, not a replaceable Ratatui-backed terminal, and Ratatui is not a runtime dependency. Choose only the package whose ownership model the application needs.