$ rezi
Getting Started

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 view function is pure and stateless; stateful reusable widgets use defineWidget with 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() and app.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?

  1. Use virtualList for long lists
  2. Provide key props for dynamic lists to enable efficient reconciliation
  3. Avoid creating new objects/functions in the view function
  4. Check the debug controller for frame timing information

On this page