Skip to main content
@rezi-ui/jsx is Rezi’s native JSX runtime. It maps JSX directly to Rezi VNodes and maintains full API parity with ui.*() factory functions from @rezi-ui/core.
Rezi’s JSX has zero React runtime overhead. It’s a pure compile-time transform that generates Rezi VNodes directly.

Installation

Install the JSX package alongside core and node:
npm install @rezi-ui/core @rezi-ui/node @rezi-ui/jsx

TypeScript Configuration

Configure your tsconfig.json to use Rezi’s JSX runtime:
tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@rezi-ui/jsx"
  }
}
This configuration uses TypeScript’s automatic JSX runtime. You don’t need to manually import anything in your files.

Basic Example

import { createNodeApp } from "@rezi-ui/node";
import { Page, Panel, Row, Text, Spacer, Button } from "@rezi-ui/jsx";

type State = { count: number };

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

app.view((state) => (
  <Page
    p={1}
    body={
      <Panel title="Counter">
        <Row gap={1} items="center">
          <Text variant="heading">Count: {state.count}</Text>
          <Spacer flex={1} />
          <Button
            id="inc"
            label="+1"
            intent="primary"
            onPress={() => app.update((s) => ({ count: s.count + 1 }))}
          />
        </Row>
      </Panel>
    }
  />
));

app.keys({ q: () => app.stop() });
await app.start();

JSX vs ui.* Factory Functions

Rezi supports two authoring styles. Choose based on your preference:

JSX Style

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

function MyView(state: State) {
  return (
    <Column gap={1}>
      <Text variant="heading">Hello, {state.name}!</Text>
      <Button id="submit" label="Submit" intent="primary" />
    </Column>
  );
}

Factory Function Style

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

function MyView(state: State) {
  return ui.column({ gap: 1 }, [
    ui.text(`Hello, ${state.name}!`, { variant: "heading" }),
    ui.button("submit", "Submit", { intent: "primary" }),
  ]);
}
Both produce identical VNode trees and have the same performance characteristics.

Component Reference

All 84+ JSX components map 1:1 to ui.* factories:

Layout & Primitives

import {
  Box,
  Row,
  Column,
  Grid,
  Spacer,
  Divider,
  Text,
  Center,
} from "@rezi-ui/jsx";

<Box p={2} bg="surface">
  <Row gap={1}>
    <Text>Left</Text>
    <Spacer flex={1} />
    <Text>Right</Text>
  </Row>
  <Divider />
  <Column gap={2}>
    <Text variant="heading">Content</Text>
  </Column>
</Box>

Form Inputs

import {
  Button,
  Input,
  Textarea,
  Checkbox,
  Select,
  RadioGroup,
  Slider,
  Field,
  Form,
} from "@rezi-ui/jsx";

<Form>
  <Field label="Name" description="Your full name">
    <Input id="name" placeholder="Enter name..." />
  </Field>
  
  <Field label="Bio">
    <Textarea id="bio" rows={5} />
  </Field>
  
  <Checkbox id="terms" label="I agree to terms" />
  
  <Button id="submit" label="Submit" intent="primary" />
</Form>
import {
  Page,
  Header,
  Panel,
  Card,
  Tabs,
  Accordion,
  Breadcrumb,
  Sidebar,
} from "@rezi-ui/jsx";

<Page
  p={1}
  gap={1}
  header={<Header title="Dashboard" subtitle="Overview" />}
  body={
    <Column gap={2}>
      <Tabs
        items={[
          { id: "overview", label: "Overview" },
          { id: "details", label: "Details" },
        ]}
        activeId="overview"
      />
      <Panel title="Content">
        <Text>Panel content here</Text>
      </Panel>
    </Column>
  }
/>

Overlays & Feedback

import {
  Modal,
  Dialog,
  Dropdown,
  CommandPalette,
  ToastContainer,
  Callout,
  Spinner,
  Progress,
  Badge,
  Status,
} from "@rezi-ui/jsx";

<>
  <Modal
    visible={state.showModal}
    title="Confirm Action"
    onClose={() => dispatch({ type: "close-modal" })}
  >
    <Text>Are you sure?</Text>
  </Modal>
  
  <Callout variant="warning">
    <Text>This action cannot be undone.</Text>
  </Callout>
  
  <Row gap={1}>
    <Badge variant="success">Active</Badge>
    <Status status="online" label="Connected" />
    <Progress value={75} max={100} />
  </Row>
</>

Data Display

import {
  Table,
  VirtualList,
  Tree,
  CodeEditor,
  DiffViewer,
  LogsConsole,
} from "@rezi-ui/jsx";

<Table
  columns={[
    { key: "name", label: "Name", width: 20 },
    { key: "status", label: "Status", width: 10 },
  ]}
  data={state.items}
  onRowPress={(row) => dispatch({ type: "select", id: row.id })}
/>

Visualization

import {
  Canvas,
  Image,
  LineChart,
  Scatter,
  Heatmap,
  BarChart,
  Gauge,
  Sparkline,
} from "@rezi-ui/jsx";

<LineChart
  width={60}
  height={20}
  series={[
    {
      id: "cpu",
      label: "CPU %",
      data: state.cpuHistory,
      color: "#0ea5e9",
    },
  ]}
  xAxis={{ label: "Time" }}
  yAxis={{ label: "Usage" }}
/>

Children Handling

Rezi’s JSX supports both array and single-child patterns:
// Single child
<Column>
  <Text>Single child</Text>
</Column>

// Multiple children
<Column>
  <Text>First</Text>
  <Text>Second</Text>
  <Text>Third</Text>
</Column>

// Array children
<Column>
  {items.map((item) => (
    <Text key={item.id}>{item.label}</Text>
  ))}
</Column>

// Fragments
<>
  <Text>One</Text>
  <Text>Two</Text>
</>

Conditional Rendering

Use JavaScript expressions for conditional rendering:
import { show, when, maybe, match } from "@rezi-ui/jsx";

// Ternary
<Column>
  {state.loading ? (
    <Spinner />
  ) : (
    <Text>Loaded</Text>
  )}
</Column>

// Logical AND
<Column>
  {state.error && <Callout variant="danger">{state.error}</Callout>}
</Column>

// Helper functions
<Column>
  {show(state.loading, <Spinner />)}
  {when(state.error, () => <Callout variant="danger">{state.error}</Callout>)}
  {maybe(state.user, (user) => <Text>Hello, {user.name}</Text>)}
  {match(state.status, {
    idle: <Badge variant="neutral">Idle</Badge>,
    loading: <Spinner />,
    success: <Badge variant="success">Done</Badge>,
    error: <Badge variant="danger">Failed</Badge>,
  })}
</Column>

List Rendering

For efficient list rendering, use the each helper with keys:
import { each } from "@rezi-ui/jsx";

<Column>
  {each(
    state.items,
    (item) => item.id,
    (item) => (
      <Row gap={1}>
        <Text>{item.name}</Text>
        <Badge>{item.status}</Badge>
      </Row>
    ),
  )}
</Column>

Custom Components with defineWidget

Create reusable components with hooks:
import { defineWidget } from "@rezi-ui/jsx";
import { Column, Text, Button } from "@rezi-ui/jsx";

type CounterProps = {
  initialValue?: number;
};

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

  return (
    <Column gap={1}>
      <Text variant="heading">Count: {count}</Text>
      <Button
        id={ctx.id("inc")}
        label="+1"
        intent="primary"
        onPress={() => setCount(count + 1)}
      />
    </Column>
  );
});

// Usage
<Counter initialValue={10} />

JSX Pragma (Per-File Override)

If you don’t want to configure JSX globally, use a per-file pragma:
/** @jsxImportSource @rezi-ui/jsx */
import { Column, Text } from "@rezi-ui/jsx";

function MyComponent() {
  return (
    <Column>
      <Text>Hello from JSX</Text>
    </Column>
  );
}

API Parity with ui.*

JSX components delegate to ui.*() factories internally. Props map directly:
JSXFactoryEquivalent
<Text variant="heading">Hi</Text>ui.text("Hi", { variant: "heading" })
<Button id="btn" label="Click" intent="primary" />ui.button("btn", "Click", { intent: "primary" })
<Row gap={1}><Text>A</Text></Row>ui.row({ gap: 1 }, [ui.text("A")])
All props, callbacks, and styling options work identically.

Performance Considerations

Rezi’s JSX is intentionally thin:
  • No virtual DOM diffing — JSX compiles to direct VNode calls
  • No reconciliation overhead — Same performance as ui.* factories
  • Zero React runtime — Smaller bundle, faster startup
  • Tree-shaking friendly — Import only the components you use

Mixing JSX and Factory Functions

You can freely mix both styles in the same codebase:
import { ui } from "@rezi-ui/core";
import { Column, Text } from "@rezi-ui/jsx";

function MyView() {
  return (
    <Column gap={1}>
      <Text>JSX style</Text>
      {ui.text("Factory style", { variant: "caption" })}
    </Column>
  );
}

Common Patterns

Form with Validation

import { Form, Field, Input, Textarea, Button, Actions, Callout } from "@rezi-ui/jsx";

function ContactForm(state: FormState, actions: FormActions) {
  return (
    <Form>
      <Field label="Name" error={state.errors.name}>
        <Input
          id="name"
          value={state.name}
          onInput={(value) => actions.setField("name", value)}
        />
      </Field>

      <Field label="Message" error={state.errors.message}>
        <Textarea
          id="message"
          value={state.message}
          rows={5}
          onInput={(value) => actions.setField("message", value)}
        />
      </Field>

      {state.submitError && (
        <Callout variant="danger">{state.submitError}</Callout>
      )}

      <Actions>
        <Button id="cancel" label="Cancel" onPress={actions.cancel} />
        <Button
          id="submit"
          label="Submit"
          intent="primary"
          disabled={!state.isValid}
          onPress={actions.submit}
        />
      </Actions>
    </Form>
  );
}

Multi-Screen App with Routing

import { Page, Header, Tabs } from "@rezi-ui/jsx";
import { match } from "@rezi-ui/jsx";

function App(state: AppState, actions: AppActions) {
  return (
    <Page
      p={1}
      gap={1}
      header={<Header title="My App" subtitle="Dashboard" />}
      body={
        <>
          <Tabs
            items={[
              { id: "home", label: "Home" },
              { id: "settings", label: "Settings" },
              { id: "logs", label: "Logs" },
            ]}
            activeId={state.route}
            onSelect={(id) => actions.navigate(id)}
          />
          {match(state.route, {
            home: <HomeScreen />,
            settings: <SettingsScreen />,
            logs: <LogsScreen />,
          })}
        </>
      }
    />
  );
}

Next Steps

Widget Catalog

Browse all available JSX components

defineWidget Guide

Create custom reusable components

Animation Hooks

Add transitions and springs to JSX components

Example Templates

See JSX in action in template projects

Build docs developers (and LLMs) love