$ rezi
Widgets

Widget API Reference

Rezi ships a comprehensive built-in widget catalog. Every widget is a plain TypeScript function that returns a VNode -- the virtual-DOM node Rezi reconciles, lays out, and renders to the terminal.

Rezi ships a comprehensive built-in widget catalog. Every widget is a plain TypeScript function that returns a VNode -- the virtual-DOM node Rezi reconciles, lays out, and renders to the terminal.

Always build your views through the ui namespace. These factories are the safest, highest-level API and are the only surface covered by deterministic VNode contract tests.

import { ui } from "@rezi-ui/core";

app.view((state) =>
  ui.column({ p: 1, gap: 1 }, [
    ui.text("Hello, World!"),
    ui.button({ id: "ok", label: "OK" }),
  ])
);

Benefits of ui.* factories:

  • Proper VNode construction with correct kind discriminants.
  • Automatic filtering of null, false, and undefined children.
  • Automatic flattening of nested child arrays.
  • Default gap: 1 applied to row/column/hstack/vstack when omitted.
  • Interactive widget props validated before layout (e.g. button requires non-empty id).

Quick-Reference Table

Layout

Container and spacing primitives for arranging widgets.

FactoryDescriptionFocusableStability
ui.box(props, children)Container with border, padding, titleNostable
ui.row(props, children)Horizontal stack layoutNostable
ui.column(props, children)Vertical stack layoutNostable
ui.hstack(...)Shorthand horizontal stack (accepts gap number, props, or just children)Nostable
ui.vstack(...)Shorthand vertical stack (accepts gap number, props, or just children)Nostable
ui.grid(props, ...children)Two-dimensional grid layoutNostable
ui.spacer(props?)Fixed-size or flexible spacingNostable
ui.divider(props?)Visual separator lineNostable
ui.layers(children) / ui.layers(props, children)Layer stack container (z-ordering)Nobeta
ui.splitPane(props, children)Resizable split layout with draggable dividersNobeta
ui.panelGroup(props, children)Container for resizable panelsNobeta
ui.resizablePanel(props?, children?)Panel within a panel groupNobeta

Convenience aliases: ui.spacedVStack(children) and ui.spacedHStack(children) are shorthand for vstack/hstack with a default gap. They also accept an explicit gap number as the first argument: ui.spacedVStack(2, children).

Quick example:

ui.column({ p: 1, gap: 1 }, [
  ui.text("Title", { variant: "heading" }),
  ui.row({ gap: 2 }, [
    ui.button({ id: "ok", label: "OK" }),
    ui.button({ id: "cancel", label: "Cancel" }),
  ]),
])

Text & Display

Content rendering, labels, and informational widgets.

FactoryDescriptionFocusableStability
ui.text(content, style?)Display text with optional styling or variantNostable
ui.richText(spans, props?)Multi-styled text spansNobeta
ui.icon(iconPath, props?)Single-character icon from the icon registryNobeta
ui.badge(text, props?)Small status indicator labelNobeta
ui.status(status, props?)Online/offline/away/busy status dotNobeta
ui.tag(text, props?)Inline label with backgroundNobeta
ui.kbd(keys, props?)Keyboard shortcut displayNobeta
ui.empty(title, props?)Empty state placeholder with optional icon/actionNobeta
ui.callout(message, props?)Alert/info message box with variantsNobeta
ui.errorDisplay(message, props?)Error message with optional retryNobeta
ui.errorBoundary(props)Isolates subtree runtime errors with fallback UINobeta

Quick example:

ui.column({ gap: 1 }, [
  ui.richText([
    { text: "Error: ", style: { fg: { r: 255, g: 0, b: 0 }, bold: true } },
    { text: "File not found" },
  ]),
  ui.callout("This action cannot be undone", { variant: "warning" }),
  ui.badge("New", { variant: "info" }),
])

Indicators

Visual feedback for loading, progress, and data density.

FactoryDescriptionFocusableStability
ui.spinner(props?)Animated loading indicatorNobeta
ui.progress(value, props?)Progress bar (value 0-1) with variantsNobeta
ui.skeleton(width, props?)Loading placeholderNobeta
ui.gauge(value, props?)Compact progress gauge with label and thresholdsNobeta

Quick example:

ui.column({ gap: 1 }, [
  ui.spinner({ variant: "dots", label: "Loading..." }),
  ui.progress(0.75, { showPercent: true }),
  ui.gauge(0.42, { label: "CPU" }),
])

Charts

Data visualization for terminal dashboards.

FactoryDescriptionFocusableStability
ui.sparkline(data, props?)Inline mini chart using block charactersNobeta
ui.barChart(data, props?)Horizontal/vertical bar chartNobeta
ui.miniChart(values, props?)Compact multi-value displayNobeta
ui.lineChart(props)Multi-series line visualization (canvas-based)Nobeta
ui.scatter(props)Cartesian scatter plot (canvas-based)Nobeta
ui.heatmap(props)Matrix heat map with color scales (canvas-based)Nobeta

Quick example:

ui.row({ gap: 2 }, [
  ui.sparkline([10, 20, 15, 30, 25], { width: 10 }),
  ui.barChart([
    { label: "TypeScript", value: 60 },
    { label: "JavaScript", value: 30 },
    { label: "Python", value: 10 },
  ], { showValues: true }),
])

Input & Forms

Interactive form controls. All form widgets require an id prop for focus management.

FactoryDescriptionFocusableStability
ui.button(id, label) / ui.button(props)Clickable button with labelYesbeta
ui.input(id, value) / ui.input(props)Single-line text inputYesstable
ui.textarea(props)Multi-line text input (multiline input variant)Yesbeta
ui.slider(props)Numeric range inputYesbeta
ui.checkbox(props)Toggle checkboxYesbeta
ui.radioGroup(props)Single-select option groupYesbeta
ui.select(props)Dropdown selectionYesbeta
ui.field(props)Form field wrapper with label, error, and hintNobeta

Quick example:

ui.form([
  ui.field({
    label: "Username",
    required: true,
    error: errors.username,
    children: ui.input("username", state.username, {
      onInput: (v) => app.update({ username: v }),
    }),
  }),
  ui.checkbox({
    id: "remember",
    checked: state.remember,
    label: "Remember me",
    onChange: (c) => app.update({ remember: c }),
  }),
  ui.actions([
    ui.button("submit", "Submit", {
      onPress: () => handleSubmit(),
    }),
  ]),
])

Widgets for navigating between views, sections, and pages.

FactoryDescriptionFocusableStability
ui.tabs(props)Tab switcher with scoped contentYesbeta
ui.accordion(props)Expand/collapse stacked sectionsYesbeta
ui.breadcrumb(props)Hierarchical location path with jumpsOptional (id)beta
ui.link(url, label?) / ui.link(props)Hyperlink text with optional press behaviorOptional (id)beta
ui.pagination(props)Navigate paged datasetsYesbeta
ui.routerBreadcrumb(router, routes, props?)Breadcrumbs derived from current router historyNobeta
ui.routerTabs(router, routes, props?)Tabs derived from registered routes with current route selectionNobeta

Quick example:

ui.tabs({
  id: "main-tabs",
  tabs: [
    { key: "overview", label: "Overview", content: OverviewPanel() },
    { key: "details", label: "Details", content: DetailsPanel() },
    { key: "logs", label: "Logs", content: LogsPanel() },
  ],
  activeTab: state.activeTab,
  onChange: (tab) => app.update({ activeTab: tab }),
})

Data

Tables, lists, and trees for structured data display.

FactoryDescriptionFocusableStability
ui.table(props)Tabular data with sorting, selection, virtualizationYesstable
ui.virtualList(props)Efficiently render large lists with virtualized scrollingYesstable
ui.tree(props)Hierarchical data with expand/collapse and selectionYesbeta

Quick example:

ui.table({
  id: "files",
  columns: [
    { key: "name", header: "Name", flex: 1, sortable: true },
    { key: "size", header: "Size", width: 10, align: "right" },
  ],
  data: files,
  getRowKey: (f) => f.id,
  selection: state.selected,
  selectionMode: "multi",
  onSelectionChange: (keys) => app.update({ selected: keys }),
})

Overlays

Modal dialogs, dropdown menus, toast notifications, and focus management.

FactoryDescriptionFocusableStability
ui.modal(props)Centered modal dialog with backdrop and focus trapNobeta
ui.dialog(props)Declarative dialog sugar over modal (multi-action)Nobeta
ui.dropdown(props)Positioned dropdown menu with auto-flipNobeta
ui.layer(props)Generic overlay layer with z-order controlNobeta
ui.toastContainer(props)Non-blocking notification stackNobeta
ui.commandPalette(props)Quick command search with async sourcesYesstable
ui.focusZone(props, children?)Focus group for Tab navigationNobeta
ui.focusTrap(props, children?)Constrain focus to regionNobeta
ui.focusAnnouncer(props?)Live text summary of the currently focused widgetNobeta

Quick example:

ui.layers([
  MainContent(),
  state.showConfirm && ui.dialog({
    id: "confirm",
    title: "Confirm Delete",
    message: "Are you sure you want to delete this item?",
    actions: [
      { label: "Delete", intent: "danger", onPress: () => deleteItem() },
      { label: "Cancel", onPress: () => app.update({ showConfirm: false }) },
    ],
    onClose: () => app.update({ showConfirm: false }),
  }),
])

Advanced

Rich, specialized widgets for IDE-like experiences.

FactoryDescriptionFocusableStability
ui.codeEditor(props)Multi-line code editing with selections, undo/redoYesbeta
ui.diffViewer(props)Unified/side-by-side diff display with hunk stagingYesbeta
ui.filePicker(props)File browser with selection and git statusYesstable
ui.fileTreeExplorer(props)File system tree view with expand/collapseYesstable
ui.logsConsole(props)Streaming log output with filteringYesbeta
ui.toolApprovalDialog(props)Tool execution review dialogYesexperimental

Quick example:

ui.codeEditor({
  id: "editor",
  lines: state.lines,
  cursor: state.cursor,
  selection: state.selection,
  scrollTop: state.scrollTop,
  scrollLeft: state.scrollLeft,
  lineNumbers: true,
  tabSize: 2,
  onChange: (lines, cursor) => app.update({ lines, cursor }),
  onScroll: (top, left) => app.update({ scrollTop: top, scrollLeft: left }),
})

Graphics

Pixel-level drawing and image rendering for the terminal.

FactoryDescriptionFocusableStability
ui.canvas(props)Pixel-level drawing surfaceNobeta
ui.image(props)Binary image rendering (PNG/RGBA)Nobeta

Quick example:

ui.canvas({
  width: 40,
  height: 20,
  draw: (ctx) => {
    ctx.fillRect(0, 0, 40, 20, "#000000");
    ctx.line(0, 0, 39, 19, "#ffffff");
  },
})

High-Level Composition Helpers

The ui namespace includes convenience wrappers that compose lower-level widgets:

HelperExpands toPurpose
ui.panel(titleOrOptions, children)ui.box({ border: "rounded", p: 1, title }, ...)Bordered panel with title; options support id, key, title, gap, p, variant, style
ui.form(children) / ui.form(options, children)ui.column({ gap: 1 }, children)Vertically stacked form layout; options support id, key, gap
ui.actions(children) / ui.actions(options, children)ui.row({ justify: "end", gap: 1 }, children)Right-aligned action button row; options support id, key, gap
ui.center(child, options?)ui.column({ width: "100%", height: "100%", align: "center", justify: "center" }, ...)Center a single widget; options support id, key, p
ui.page(options)ui.column(...) with optional header/body/footerFull-page layout scaffold
ui.keybindingHelp(bindings, options?)Formatted table of keyboard shortcutsKeyboard shortcut reference; options: title ("Keyboard Shortcuts"), emptyText ("No shortcuts registered."), showMode (auto), sort (true)
ui.page({
  header: ui.text("My App", { style: { bold: true } }),
  body: ui.panel("Content", [
    ui.text("Main content goes here"),
    ui.form([
      ui.field({ label: "Name", children: ui.input("name", state.name) }),
      ui.actions([
        ui.button("save", "Save"),
        ui.button("cancel", "Cancel"),
      ]),
    ]),
  ]),
  footer: ui.text("Press Ctrl+Q to quit", { dim: true }),
})

Composition Patterns

each() for Lists

Render a list of items with automatic key injection and optional empty state:

import { each } from "@rezi-ui/core";

each(
  state.items,
  (item, index) => ui.text(`${index + 1}. ${item.name}`),
  {
    key: (item) => item.id,
    container: "column",  // default; also accepts "row"
    empty: () => ui.text("No items yet", { dim: true }),
  },
)

For inline usage within a children array (returns VNode[] instead of a container):

import { eachInline } from "@rezi-ui/core";

ui.column({ gap: 1 }, [
  ui.text("Items:"),
  ...eachInline(
    state.items,
    (item) => ui.text(item.name),
    { key: (item) => item.id },
  ),
])

show() / when() / maybe() / match() for Conditionals

import { show, when, maybe, match } from "@rezi-ui/core";

ui.column({}, [
  // show(condition, vnode, fallback?) -- eagerly evaluated
  show(state.isLoggedIn, ui.text("Welcome back!")),
  show(state.isLoggedIn, ui.text("Welcome!"), ui.text("Please log in")),

  // when(condition, trueFn, falseFn?) -- lazily evaluated
  when(
    state.items.length > 0,
    () => ItemList(),
    () => ui.empty("No items"),
  ),

  // maybe(value, render) -- null-safe rendering
  maybe(state.currentUser, (user) =>
    ui.text(`Logged in as ${user.name}`),
  ),

  // match(value, cases) -- pattern matching with _ default
  match(state.status, {
    loading: () => ui.spinner(),
    error: () => ui.errorDisplay("Something went wrong"),
    _: () => ui.text("Ready"),
  }),
])

You can also use plain JavaScript expressions -- falsy values (false, null, undefined) are automatically filtered from children arrays:

ui.column({}, [
  ui.text("Always visible"),
  state.showDetails && ui.text("Conditionally visible"),
])

defineWidget() for Reusable Components

Create stateful, reusable components with local state and lifecycle hooks:

import { defineWidget, ui } from "@rezi-ui/core";

const Counter = defineWidget<{ initial: number; key?: string }>(
  (props, ctx) => {
    const [count, setCount] = ctx.useState(props.initial);

    ctx.useEffect(() => {
      console.log(`Counter mounted with initial=${props.initial}`);
      return () => console.log("Counter unmounted");
    }, []);

    return ui.row({ gap: 1 }, [
      ui.text(`Count: ${count}`),
      ui.button({
        id: ctx.id("inc"),
        label: "+",
        onPress: () => setCount((c) => c + 1),
      }),
      ui.button({
        id: ctx.id("dec"),
        label: "-",
        onPress: () => setCount((c) => c - 1),
      }),
    ]);
  },
  { name: "Counter" },
);

// Usage in a view:
ui.column([
  Counter({ initial: 0 }),
  Counter({ initial: 10, key: "counter-2" }),
]);

See the Composition Guide for full details on defineWidget, WidgetContext, and hook usage.

Stability Tiers

Widget stability tiers and guarantees are documented in Widget Stability.

TierMeaning
stableSemver-protected behavior contract with deterministic regression tests. No breaking changes in minor/patch releases.
betaCore invariants tested; contract may evolve in minor releases.
experimentalNo compatibility guarantees; APIs can change at any time.

Common Props Reference

Identity and Reconciliation

// Unique key for reconciliation (required for dynamic lists)
key?: string

// Interactive widget ID (required for all focusable widgets)
id: string

// Optional semantic label for accessibility / focus announcements
accessibleLabel?: string

// Whether this widget can receive focus (default depends on widget kind)
focusable?: boolean

Spacing Props

box, row, column, hstack, and vstack all accept spacing props. Values are either a number (terminal cells) or a named key.

// Padding
p?: SpacingValue    // All sides
px?: SpacingValue   // Horizontal (left + right)
py?: SpacingValue   // Vertical (top + bottom)
pt?: SpacingValue   // Top
pr?: SpacingValue   // Right
pb?: SpacingValue   // Bottom
pl?: SpacingValue   // Left

// Margin
m?: SpacingValue
mx?: SpacingValue
my?: SpacingValue
mt?: SpacingValue
mr?: SpacingValue
mb?: SpacingValue
ml?: SpacingValue

Spacing Value Scale

KeyCells
"none"0
"xs"1
"sm"1
"md"2
"lg"3
"xl"4
"2xl"6

Numbers are also accepted directly:

ui.box({ p: "md" }, [...])   // 2 cells of padding on all sides
ui.column({ p: 2, gap: 1 }, [...])  // equivalent numeric form

Layout Props

// Dimensions
width?: number | string    // Fixed width or percentage ("100%")
height?: number | string   // Fixed height or percentage
minWidth?: number
maxWidth?: number
minHeight?: number
maxHeight?: number
flex?: number              // Flex grow factor

// Gap between children (row, column, hstack, vstack)
gap?: SpacingValue

// Alignment
align?: "start" | "center" | "end" | "stretch"
justify?: "start" | "end" | "center" | "between" | "around" | "evenly"
items?: "start" | "center" | "end" | "stretch"

Visual Props

// Border style (box)
border?: "none" | "single" | "double" | "rounded" | "heavy" | "dashed"

// Box title (displayed in border)
title?: string
titleAlign?: "left" | "center" | "right"

// Text style (text, richText)
style?: TextStyle

Grid-Specific Props

grid uses its own layout system and does not accept spacing props like p/m:

ui.grid({
  columns: 3,           // Number of columns or explicit sizes
  rows: 2,              // Number of rows or explicit sizes
  gap: 1,               // Uniform gap
  rowGap: 1,            // Row-specific gap
  columnGap: 2,         // Column-specific gap
}, child1, child2, child3, child4, child5, child6)

Event Handlers

Interactive widgets fire event callbacks for both keyboard and mouse input:

ui.button({
  id: "submit",
  label: "Submit",
  onPress: () => handleSubmit(),   // Fires on Enter, Space, or mouse click
})

ui.input({
  id: "name",
  value: state.name,
  onInput: (value) => app.update({ name: value }),
  onBlur: () => validateField("name"),
})

Mouse Support

All focusable widgets can be clicked with the mouse to receive focus. Scrollable widgets (virtualList, codeEditor, logsConsole, diffViewer) respond to the mouse scroll wheel. splitPane dividers can be dragged to resize panels. See the Mouse Support Guide for details.

VNode Factory Guarantees

ui.* factories are contract-tested for deterministic VNode creation:

  • Factories that expose a key prop forward it to the resulting VNode for reconciliation.
  • Container-style child arrays filter null, false, and undefined values.
  • Nested child arrays are flattened before VNode children are stored.
  • Interactive widgets validate required runtime props before layout:
    • button: non-empty id; label must be a string (empty allowed).
    • input: non-empty id; value must be a string.
    • textarea: non-empty id; value must be a string; optional rows controls visible height.
    • select: non-empty id; value must be a string; options must be an array (empty allowed).
    • slider: non-empty id, finite numeric range with min <= max, step > 0.
    • checkbox: non-empty id, boolean checked.
    • radioGroup: non-empty id; value must be a string; options must be non-empty.

API Reference

For complete type definitions, see the API Reference.

On this page