Frequently Asked Questions
General
What is Rezi?
Rezi is a code-first terminal UI framework for Node.js and Bun. It provides a declarative widget API for building terminal applications with automatic focus management, theming, and keyboard navigation.
Is Rezi like React for the terminal?
No. While Rezi uses a declarative widget tree similar to React's component model, it has a different architecture:
- No virtual DOM diffing - rendering goes through a binary drawlist protocol to a native C engine
- No React-style component lifecycle - the root
viewfunction is pure and stateless; stateful reusable widgets usedefineWidgetwith hooks (useState,useRef,useEffect) - Binary protocol boundary with the Zireael C engine for terminal I/O
- Deterministic rendering with no side effects in the view function
Rezi is designed specifically for terminal UIs, not as a React port.
I'm migrating from another terminal UI stack. Where should I start?
Start with the Rezi-native API:
- use
createNodeApp(...)for app creation - use
ui.*for the canonical widget tree API - use
app.keys()andapp.modes()for input handling
The best entry points are Quickstart, Create Rezi, and Concepts.
What platforms does Rezi support?
Rezi supports:
- Linux: x64, arm64 (glibc)
- macOS: x64 (Intel), arm64 (Apple Silicon)
- Windows: x64
Prebuilt native binaries are included for all supported platforms.
What runtime versions are supported?
Rezi supports:
- Node.js 18+ (18.18+ recommended)
- Bun 1.3+
Architecture
Why does Rezi use a native C engine?
The Zireael C engine provides:
- Fast framebuffer diffing and terminal output
- Terminal capability detection
- Platform-specific optimizations
- A strict binary ABI boundary (drawlist in, events out)
This architecture enables high performance while keeping the TypeScript code portable.
Does @rezi-ui/core work outside Node.js?
@rezi-ui/core is runtime-agnostic by design. It contains no Node.js-specific APIs (no Buffer, worker_threads, fs, etc.).
Node.js and Bun integration is provided by @rezi-ui/node. Additional backends for other runtimes could be implemented using the same core package.
What is the binary protocol?
Rezi uses two binary formats for communication with the native engine:
- ZRDL (Drawlist): Rendering commands sent from TypeScript to the engine
- ZREV (Event Batch): Input events sent from the engine to TypeScript
Both formats are versioned, little-endian, and 4-byte aligned. See the Protocol documentation for details.
Usage
How do I update application state?
Use app.update() with either a new state object or an updater function:
// Direct state
app.update({ count: 5 });
// Updater function (recommended for derived state)
app.update((prev) => ({ count: prev.count + 1 }));Updates are batched and coalesced for efficiency.
How do I handle keyboard input?
Use app.keys() to register keybindings:
app.keys({
"ctrl+s": () => save(),
"ctrl+q": () => app.stop(),
"j": () => moveDown(),
"k": () => moveUp(),
});For modal keybindings (like Vim modes), use app.modes():
app.modes({
normal: {
"i": () => app.setMode("insert"),
"j": () => moveCursorDown(),
},
insert: {
"escape": () => app.setMode("normal"),
},
});
app.setMode("normal");How do I style widgets?
Use the style prop with RGB colors and text attributes:
import { rgb } from "@rezi-ui/core";
ui.text("Error", {
style: {
fg: rgb(255, 100, 100),
bold: true,
},
});Or use built-in themes:
import { darkTheme, nordTheme } from "@rezi-ui/core";
app.setTheme(nordTheme);How do I show a modal dialog?
Use the ui.layers() and ui.modal() widgets:
ui.layers([
MainScreen(state),
state.showModal &&
ui.modal({
id: "confirm",
title: "Confirm Action",
content: ui.text("Are you sure?"),
actions: [
ui.button({ id: "yes", label: "Yes", onPress: handleConfirm }),
ui.button({ id: "no", label: "No", onPress: closeModal }),
],
onClose: closeModal,
}),
]);How do I render a list efficiently?
For small lists, use ui.column() with mapped items:
ui.column({}, items.map((item) => ui.text(item.name, { key: item.id })))For large lists (hundreds or thousands of items), use ui.virtualList():
ui.virtualList({
id: "items",
items: largeList,
itemHeight: 1,
renderItem: (item, index, focused) =>
ui.text(focused ? `> ${item.name}` : ` ${item.name}`),
})Debugging
How do I debug rendering issues?
Enable the debug controller:
import { createDebugController, categoriesToMask } from "@rezi-ui/core";
import { createNodeApp } from "@rezi-ui/node";
const app = createNodeApp({ initialState: {} });
const debug = createDebugController({ backend: app.backend.debug });
await debug.enable({
minSeverity: "info",
categoryMask: categoriesToMask(["frame", "error"]),
});
// Pull recent records on demand:
const records = await debug.query({ maxRecords: 200 });Use the debug panel widget:
import { debugPanel } from "@rezi-ui/core";
ui.layers([
MainContent(),
debugPanel({ position: "bottom-right" }),
]);My app is slow. How do I optimize it?
- Use
virtualListfor long lists - Provide
keyprops for dynamic lists to enable efficient reconciliation - Avoid creating new objects/functions in the view function
- Check the debug controller for frame timing information