Skip to content
UrushiDocumentation

Components

Components keep reusable meaning separate from terminal presentation. Build an immutable data value, then ask a Theme or an explicit presentation policy to compose it into a View.

Run the complete Components quickstart ↓

Content Data Canonical presentation
Ordered or nested entries List<T> theme.list(&list)
Rows and columns with headers and rules Table<Row> theme.table(&table)
A hierarchy Tree<T> theme.tree(&tree)
Position inside finite content Scrollbar theme.scrollbar(&scrollbar)

Every core component has three layers:

How components become Views

Component meaning and presentation policy are composed into the same renderer-neutral View used everywhere else.

  1. dataSemantic dataContent and semantic state
  2. plus
    dataPresentation policyFormatting, glyphs, spacing, borders, and styles
  3. apply
    processComposeSnapshot data and presentation together
  4. result
    dataViewAn immutable renderer-neutral value
  • The data value owns content and semantic state, such as list nesting or a scrollbar’s content and viewport lengths.
  • The presentation owns formatting, glyphs, spacing, borders, and styles.
  • Composition snapshots both into an ordinary renderer-neutral View.

Theme supplies the canonical presentation for each component. Use it for the ordinary case; construct or clone a presentation policy when one component needs a different visual rule.

Components do not write output, read events, own focus, or retain an event loop. The caller owns interaction state and produces new component data when it changes.

List, Table, Tree, and Scrollbar presentations may use Canvas internally where intrinsic geometry or connected lines require it. That is an implementation of their presentation, not their meaning. Application code can also place the resulting View into Canvas when a semantic component needs explicit coordinates.

Terminal window
cargo new component-demo
cd component-demo
cargo add urushi

Replace src/main.rs with:

use std::io;
use urushi::{Table, ThemePreset};
fn main() -> io::Result<()> {
let theme = ThemePreset::get("Catppuccin Mocha")
.expect("built-in theme")
.theme();
let table = Table::text()
.headers(["Package", "Status"])
.row(["urushi", "ready"])
.row(["urushi-prompt", "ready"]);
urushi::println_view(&theme.table(&table))
}

Run it with cargo run:

Rendered output
┌───────────────┬────────┐
│ Package │ Status │
├───────────────┼────────┤
│ urushi │ ready │
│ urushi-prompt │ ready │
└───────────────┴────────┘

The table owns data, the theme owns its canonical border and role styles, and theme.table returns the View that println_view resolves and writes.

Use Layout for one-off geometry without reusable meaning. Use Canvas for coordinates, overlap, or connected drawing.