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.
Recommended Approach: ui.* Factory Functions¶
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.page({
p: 1,
gap: 1,
header: ui.header({ title: "Hello, World!" }),
body: ui.panel("Actions", [
ui.actions([ui.button({ id: "ok", label: "OK", intent: "primary" })]),
]),
})
);
Benefits of ui.* factories:
- Proper VNode construction with correct
kinddiscriminants. - Automatic filtering of
null,false, andundefinedchildren. - Automatic flattening of nested child arrays.
- Default
gap: 1applied torow/columnwhen omitted. - Interactive widget props validated before layout (e.g.
buttonrequires non-emptyid).
Widget Kind Mapping Notes¶
ui.textarea(...)is a multiline variant ofui.input(...)and compiles to VNode kind"input"withmultiline: true.- In tests,
createTestRenderer(...).render(...).findAll("textarea")matches those multiline input nodes.
Beautiful Defaults¶
Core interactive widgets are recipe-styled by default (buttons, inputs,
selects, checkboxes, progress bars, callouts). Use intent on buttons for
common patterns (primary/danger/link), and use manual style props to
override specific attributes (they do not disable recipes).
Quick-Reference Table¶
Layout¶
Container and spacing primitives for arranging widgets.
| Factory | Description | Focusable | Stability |
|---|---|---|---|
ui.box(props, children) |
Container with border, padding, title, and optional transition/exitTransition/opacity props |
No | stable |
ui.row(props, children) |
Horizontal stack layout (supports transition for animated layout changes) |
No | stable |
ui.column(props, children) |
Vertical stack layout (supports transition for animated layout changes) |
No | stable |
ui.grid(props, ...children) |
Two-dimensional grid layout (supports transition for animated layout changes) |
No | stable |
ui.spacer(props?) |
Fixed-size or flexible spacing | No | stable |
ui.divider(props?) |
Visual separator line | No | stable |
ui.layers(children) / ui.layers(props, children) |
Layer stack container (z-ordering) | No | beta |
ui.splitPane(props, children) |
Resizable split layout with draggable dividers | No | beta |
ui.panelGroup(props, children) |
Container for resizable panels | No | beta |
ui.resizablePanel(props?, children?) |
Panel within a panel group | No | beta |
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.
| Factory | Description | Focusable | Stability |
|---|---|---|---|
ui.text(content, style?) |
Display text with optional styling or variant | No | stable |
ui.richText(spans, props?) |
Multi-styled text spans | No | beta |
ui.icon(iconPath, props?) |
Single-character icon from the icon registry | No | beta |
ui.badge(text, props?) |
Small status indicator label | No | beta |
ui.status(status, props?) |
Online/offline/away/busy status dot | No | beta |
ui.tag(text, props?) |
Inline label with background | No | beta |
ui.kbd(keys, props?) |
Keyboard shortcut display | No | beta |
ui.empty(title, props?) |
Empty state placeholder with optional icon/action | No | beta |
ui.callout(message, props?) |
Alert/info message box with variants | No | beta |
ui.errorDisplay(message, props?) |
Error message with optional retry | No | beta |
ui.errorBoundary(props) |
Isolates subtree runtime errors with fallback UI | No | beta |
Quick example:
import { rgb, ui } from "@rezi-ui/core";
ui.column({ gap: 1 }, [
ui.richText([
{ text: "Error: ", style: { fg: rgb(255, 0, 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.
| Factory | Description | Focusable | Stability |
|---|---|---|---|
ui.spinner(props?) |
Animated loading indicator | No | beta |
ui.progress(value, props?) |
Progress bar (value 0-1) with variants | No | beta |
ui.skeleton(width, props?) |
Loading placeholder | No | beta |
ui.gauge(value, props?) |
Compact progress gauge with label and thresholds | No | beta |
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.
| Factory | Description | Focusable | Stability |
|---|---|---|---|
ui.sparkline(data, props?) |
Inline mini chart using block characters | No | beta |
ui.barChart(data, props?) |
Horizontal/vertical bar chart | No | beta |
ui.miniChart(values, props?) |
Compact multi-value display | No | beta |
ui.lineChart(props) |
Multi-series line visualization (canvas-based) | No | beta |
ui.scatter(props) |
Cartesian scatter plot (canvas-based) | No | beta |
ui.heatmap(props) |
Matrix heat map with color scales (canvas-based) | No | beta |
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.
| Factory | Description | Focusable | Stability |
|---|---|---|---|
ui.button(props) |
Clickable button with label | Yes | beta |
ui.input(props) |
Single-line text input | Yes | stable |
ui.textarea(props) |
Multi-line text input (multiline input variant) | Yes | beta |
ui.slider(props) |
Numeric range input | Yes | beta |
ui.checkbox(props) |
Toggle checkbox | Yes | beta |
ui.radioGroup(props) |
Single-select option group | Yes | beta |
ui.select(props) |
Dropdown selection | Yes | beta |
ui.field(props) |
Form field wrapper with label, error, and hint | No | beta |
Quick example:
ui.form([
ui.field({
label: "Username",
required: true,
error: errors.username,
children: ui.input({
id: "username",
value: 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({
id: "submit",
label: "Submit",
onPress: () => handleSubmit(),
}),
]),
])
Navigation¶
Widgets for navigating between views, sections, and pages.
| Factory | Description | Focusable | Stability |
|---|---|---|---|
ui.tabs(props) |
Tab switcher with scoped content | Yes | beta |
ui.accordion(props) |
Expand/collapse stacked sections | Yes | beta |
ui.breadcrumb(props) |
Hierarchical location path with jumps | Optional (id) |
beta |
ui.link(props) |
Hyperlink text with optional press behavior | Optional (id) |
beta |
ui.pagination(props) |
Navigate paged datasets | Yes | beta |
ui.routerBreadcrumb(router, routes, props?) |
Breadcrumbs derived from current router history | No | beta |
ui.routerTabs(router, routes, props?) |
Tabs derived from registered routes with current route selection | No | beta |
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.
| Factory | Description | Focusable | Stability |
|---|---|---|---|
ui.table(props) |
Tabular data with sorting, selection, virtualization | Yes | stable |
ui.virtualList(props) |
Efficiently render large lists with virtualized scrolling | Yes | stable |
ui.tree(props) |
Hierarchical data with expand/collapse and selection | Yes | beta |
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.
| Factory | Description | Focusable | Stability |
|---|---|---|---|
ui.modal(props) |
Centered modal dialog with backdrop and focus trap | No | beta |
ui.dialog(props) |
Declarative dialog sugar over modal (multi-action) | No | beta |
ui.dropdown(props) |
Positioned dropdown menu with auto-flip | No | beta |
ui.layer(props) |
Generic overlay layer with z-order control | No | beta |
ui.toastContainer(props) |
Non-blocking notification stack | No | beta |
ui.commandPalette(props) |
Quick command search with async sources | Yes | stable |
ui.focusZone(props, children?) |
Focus group for Tab navigation | No | beta |
ui.focusTrap(props, children?) |
Constrain focus to region | No | beta |
ui.focusAnnouncer(props?) |
Live text summary of the currently focused widget | No | beta |
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.
| Factory | Description | Focusable | Stability |
|---|---|---|---|
ui.codeEditor(props) |
Multi-line code editing with selections, undo/redo | Yes | beta |
ui.diffViewer(props) |
Unified/side-by-side diff display with hunk staging | Yes | beta |
ui.filePicker(props) |
File browser with selection and git status | Yes | stable |
ui.fileTreeExplorer(props) |
File system tree view with expand/collapse | Yes | stable |
ui.logsConsole(props) |
Streaming log output with filtering | Yes | beta |
ui.toolApprovalDialog(props) |
Tool execution review dialog | Yes | experimental |
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.
| Factory | Description | Focusable | Stability |
|---|---|---|---|
ui.canvas(props) |
Pixel-level drawing surface | No | beta |
ui.image(props) |
Binary image rendering (PNG/RGBA) | No | beta |
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:
| Helper | Expands to | Purpose |
|---|---|---|
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: "full", height: "full", align: "center", justify: "center" }, ...) |
Center a single widget; options support id, key, p |
ui.page(options) |
ui.column(...) with optional header/body/footer |
Full-page layout scaffold; supports full LayoutConstraints (width, height, min/max, flex, display, etc.) |
ui.appShell(options) |
ui.page(...) with standard header/sidebar/body/footer layout |
Full app scaffold (header + optional sidebar + body + footer); supports full LayoutConstraints plus constraint-capable sidebar.width |
ui.card(titleOrOptions, children) |
ui.box({ border: "rounded", p: 1 }, ...) |
Elevated content block with optional title/subtitle/actions |
ui.toolbar(children) / ui.toolbar(options, children) |
ui.row({ items: "center", wrap: true }, ...) |
Inline action bar |
ui.statusBar(options) |
ui.row({ width: "full" }, [...left, spacer(1), ...right]) |
Left/right status strip |
ui.header(options) |
ui.box({ border: "rounded", px: 1 }, ...) |
Standard header bar (title/subtitle/actions) |
ui.sidebar(options) |
ui.box({ border: \"rounded\", width, p: 1 }, ...) |
Navigation panel with selectable buttons |
ui.masterDetail(options) |
ui.row([...master, detail]) |
Split master/detail layout |
ui.keybindingHelp(bindings, options?) |
Formatted table of keyboard shortcuts | Keyboard shortcut reference; options: title ("Keyboard Shortcuts"), emptyText ("No shortcuts registered."), showMode (auto), sort (true) |
ui.page({
header: ui.header({ title: "My App" }),
body: ui.panel("Content", [
ui.text("Main content goes here"),
ui.form([
ui.field({ label: "Name", children: ui.input({ id: "name", value: state.name }) }),
ui.actions([
ui.button({ id: "save", label: "Save", intent: "primary" }),
ui.button({ id: "cancel", label: "Cancel" }),
]),
]),
]),
footer: ui.text("Press Ctrl+Q to quit", { dim: true }),
})
import { ui, visibilityConstraints, widthConstraints } from "@rezi-ui/core";
ui.appShell({
display: visibilityConstraints.viewportWidthAtLeast(80),
width: widthConstraints.percentOfParent(0.95),
sidebar: {
width: widthConstraints.clampedPercentOfParent({ ratio: 0.22, min: 18, max: 30 }),
content: ui.sidebar({ items, selected, onSelect: (id) => app.update({ selected: id }) }),
},
body: ui.panel("Content", [ui.text("Main view")]),
})
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.
| Tier | Meaning |
|---|---|
stable |
Strongest contract-hardened tier inside the current pre-alpha line. Deterministic regression tests exist, but repo-wide pre-alpha status still applies. |
beta |
Core invariants tested; contract may evolve in minor releases. |
experimental |
No 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, and column 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¶
| Key | Cells |
|---|---|
"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 | "full" | "auto" | fluid(...) | expr(...)
height?: number | "full" | "auto" | fluid(...) | expr(...)
minWidth?: number
maxWidth?: number
minHeight?: number
maxHeight?: number
flex?: number // Flex grow factor
flexShrink?: number // Overflow shrink factor (default 0)
flexBasis?: number | "full" | "auto" | fluid(...) | expr(...)
alignSelf?: "auto" | "start" | "center" | "end" | "stretch"
position?: "static" | "absolute"
top?: number
right?: number
bottom?: number
left?: number
// Gap between children (row, column)
gap?: SpacingValue
// Alignment
align?: "start" | "center" | "end" | "stretch"
justify?: "start" | "end" | "center" | "between" | "around" | "evenly"
items?: "start" | "center" | "end" | "stretch"
// Grid child placement (row/column/box children inside ui.grid)
gridColumn?: number // 1-based
gridRow?: number // 1-based
colSpan?: number // default 1
rowSpan?: number // default 1
width/height and related layout scalar props support fluid(min, max, options?) and expr("...").
Responsive-map layout constraints (for example { sm, md, lg, xl }) and % size strings are removed in the breaking alpha.
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)
Grid placement props are set on children (gridColumn, gridRow, colSpan, rowSpan).
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
keyprop forward it to the resulting VNode for reconciliation. - Container-style child arrays filter
null,false, andundefinedvalues. - Nested child arrays are flattened before VNode children are stored.
- Interactive widgets validate required runtime props before layout:
button: non-emptyid;labelmust be a string (empty allowed).input: non-emptyid;valuemust be a string.textarea: non-emptyid;valuemust be a string; optionalrowscontrols visible height.select: non-emptyid;valuemust be a string;optionsmust be an array (empty allowed).slider: non-emptyid, finite numeric range withmin <= max,step > 0.checkbox: non-emptyid, booleanchecked.radioGroup: non-emptyid;valuemust be a string;optionsmust be non-empty.
API Reference¶
For complete type definitions, see the API Reference.