Skip to main content

Installation

npm install @rezi-ui/jsx @rezi-ui/core

Overview

@rezi-ui/jsx provides JSX/TSX support for Rezi, allowing you to write UIs with familiar JSX syntax instead of ui.* factory functions. It offers:
  • Full JSX components for all Rezi widgets
  • TypeScript autocomplete and type checking
  • 1:1 parity with ui.* factories
  • Fragment support
  • Works with React/Preact JSX transform

TSConfig Setup

Configure TypeScript to use the Rezi JSX runtime:
tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@rezi-ui/jsx",
    "types": ["@rezi-ui/jsx"]
  }
}
For automatic JSX runtime (React 17+):
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@rezi-ui/jsx"
  }
}

Quick Start

import { createNodeApp } from "@rezi-ui/node";
import { Text, Button, Column } from "@rezi-ui/jsx";

type State = { count: number };

const app = createNodeApp<State>({
  initialState: { count: 0 },
});

app.view((state) => (
  <Column p={1}>
    <Text>Count: {state.count}</Text>
    <Button id="inc" label="Increment" />
  </Column>
));

app.on("press", (evt) => {
  if (evt.id === "inc") {
    app.update((s) => ({ count: s.count + 1 }));
  }
});

await app.run();

Component Reference

All widgets have corresponding JSX components with identical props:

Layout Components

Column
component
Vertical flex container. Maps to ui.column().
Row
component
Horizontal flex container. Maps to ui.row().
Box
component
Flexible container with synthetic inner column. Maps to ui.box().
Grid
component
CSS Grid-like layout container. Maps to ui.grid().
import { Column, Row, Box, Grid } from "@rezi-ui/jsx";

<Column gap={2} p={1}>
  <Row justifyContent="space-between">
    <Text>Left</Text>
    <Text>Right</Text>
  </Row>

  <Box border="single" p={2}>
    <Text>Boxed content</Text>
  </Box>

  <Grid cols={3} gap={1}>
    <Text>A</Text>
    <Text>B</Text>
    <Text>C</Text>
  </Grid>
</Column>

Text & Content

Text
component
Inline text node. Maps to ui.text().
RichText
component
Styled text with multiple spans. Maps to ui.richText().
Icon
component
Icon display from icon registry. Maps to ui.icon().
Kbd
component
Keyboard shortcut display. Maps to ui.kbd().
import { Text, RichText, Icon, Kbd } from "@rezi-ui/jsx";

<Column>
  <Text variant="heading">Welcome</Text>
  <RichText
    spans={[
      { text: "Bold", bold: true },
      { text: " and ", fg: { r: 100, g: 100, b: 100 } },
      { text: "italic", italic: true },
    ]}
  />
  <Icon name="file/folder" />
  <Kbd keys={["Ctrl", "C"]} />
</Column>

Interactive Widgets

Button
component
Pressable button with design system styling. Maps to ui.button().
Input
component
Single-line text input. Maps to ui.input().
Textarea
component
Multi-line text input. Maps to ui.textarea().
Checkbox
component
Boolean checkbox control. Maps to ui.checkbox().
Select
component
Dropdown select menu. Maps to ui.select().
Slider
component
Numeric slider control. Maps to ui.slider().
import { Button, Input, Checkbox, Select } from "@rezi-ui/jsx";

<Column gap={1}>
  <Button id="save" label="Save" intent="primary" />
  <Input id="name" placeholder="Enter name..." />
  <Checkbox id="agree" label="I agree to terms" />
  <Select
    id="theme"
    options={[
      { value: "dark", label: "Dark" },
      { value: "light", label: "Light" },
    ]}
  />
</Column>

Structural Widgets

Page
component
Root page container. Maps to ui.page().
Panel
component
Titled content panel. Maps to ui.panel().
Card
component
Elevated card container. Maps to ui.card().
Modal
component
Centered modal dialog. Maps to ui.modal().
Tabs
component
Tab navigation widget. Maps to ui.tabs().
import { Page, Panel, Card, Modal, Tabs } from "@rezi-ui/jsx";

<Page p={1}>
  <Panel title="Dashboard">
    <Card>
      <Text>Card content</Text>
    </Card>
  </Panel>

  <Tabs
    items={[
      { id: "home", label: "Home" },
      { id: "settings", label: "Settings" },
    ]}
    activeId="home"
  />

  <Modal id="confirm" title="Confirm Action" open={showModal}>
    <Text>Are you sure?</Text>
  </Modal>
</Page>

Advanced Widgets

Table
component
Data table with sorting and selection. Maps to ui.table().
VirtualList
component
Virtualized scrolling list. Maps to ui.virtualList().
Tree
component
Hierarchical tree view. Maps to ui.tree().
CodeEditor
component
Code editor with syntax highlighting. Maps to ui.codeEditor().
CommandPalette
component
Command palette with fuzzy search. Maps to ui.commandPalette().
import { Table, VirtualList, Tree, CodeEditor } from "@rezi-ui/jsx";

<Column>
  <Table
    id="users"
    columns={[
      { key: "name", label: "Name", width: 20 },
      { key: "email", label: "Email", width: 30 },
    ]}
    rows={users}
  />

  <VirtualList
    id="logs"
    items={logEntries}
    renderItem={(item) => <Text>{item.message}</Text>}
    height={20}
  />

  <Tree id="files" data={fileTree} />

  <CodeEditor
    id="editor"
    value={code}
    language="typescript"
    height={30}
  />
</Column>

Children Handling

JSX children are normalized automatically:
// String children become text nodes
<Column>
  Hello world
  {"Dynamic text: " + value}
</Column>

// Arrays are flattened
<Column>
  {items.map((item) => (
    <Text key={item.id}>{item.name}</Text>
  ))}
</Column>

// Fragments work
<Column>
  <>
    <Text>First</Text>
    <Text>Second</Text>
  </>
</Column>

Composition with defineWidget

defineWidget works seamlessly with JSX:
import { defineWidget } from "@rezi-ui/jsx";
import { Row, Text, Button } from "@rezi-ui/jsx";

type CounterProps = {
  initial: number;
  key?: string;
};

const Counter = defineWidget<CounterProps>((props, ctx) => {
  const [count, setCount] = ctx.useState(props.initial);

  return (
    <Row gap={1}>
      <Text>Count: {count}</Text>
      <Button
        id={ctx.id("inc")}
        label="+"
        onPress={() => setCount((c) => c + 1)}
      />
      <Button
        id={ctx.id("dec")}
        label="-"
        onPress={() => setCount((c) => c - 1)}
      />
    </Row>
  );
});

// Usage
<Column>
  <Counter initial={0} />
  <Counter initial={10} key="counter-2" />
</Column>

Control Flow

All control flow utilities work with JSX:
import { each, show, when, match } from "@rezi-ui/jsx";
import { Column, Text } from "@rezi-ui/jsx";

const items = ["Apple", "Banana", "Cherry"];
const status = "loading";

<Column>
  {/* Map with keying */}
  {each(items, (item) => (
    <Text>{item}</Text>
  ))}

  {/* Conditional rendering */}
  {show(items.length === 0, () => (
    <Text>No items</Text>
  ))}

  {/* If/else */}
  {when(
    items.length > 5,
    () => <Text>Many items</Text>,
    () => <Text>Few items</Text>
  )}

  {/* Pattern matching */}
  {match(status, {
    loading: () => <Text>Loading...</Text>,
    error: () => <Text fg={{ r: 255, g: 0, b: 0 }}>Error!</Text>,
    success: () => <Text>Done!</Text>,
  })}
</Column>

Type Safety

All JSX components are fully typed:
import type {
  ButtonJsxProps,
  ColumnJsxProps,
  TableJsxProps,
} from "@rezi-ui/jsx";

// Props are validated at compile time
<Button
  id="btn" // Required
  label="Click" // Required
  intent="primary" // Type-checked
  // @ts-expect-error - invalid prop
  invalidProp="value"
/>

Re-exports from Core

Common utilities are re-exported for convenience:
import {
  // Composition
  defineWidget,
  // Control flow
  each,
  show,
  when,
  match,
  maybe,
  // Styling
  rgb,
  recipe,
  // Types
  type WidgetContext,
  type VNode,
  type Rgb,
  type TextStyle,
} from "@rezi-ui/jsx";

createElement API

Low-level JSX transform function (usually not needed):
import { createElement, h } from "@rezi-ui/jsx";

// h is an alias for createElement
const element = h("text", { children: ["Hello"] });

Migration from ui.*

JSX components map 1:1 to ui.* factories:
<Column gap={2} p={1}>
  <Text variant="heading">Title</Text>
  <Button id="btn" label="Click" intent="primary" />
</Column>
Key differences:
  • JSX: Props are attributes, children are nested
  • ui.*: Props and children are separate arguments
  • JSX: String children auto-convert to text nodes
  • ui.*: Must explicitly call ui.text()

Performance

JSX has no runtime overhead compared to ui.* factories. The JSX transform compiles directly to VNode creation.

@rezi-ui/core

Core framework APIs

Widget Authoring

Build custom widgets

JSX Guide

JSX usage guide

Widget Reference

Complete widget catalog

Build docs developers (and LLMs) love