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:
{
"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
JSX Syntax
Equivalent ui.* syntax
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
Vertical flex container. Maps to ui.column().
Horizontal flex container. Maps to ui.row().
Flexible container with synthetic inner column. Maps to ui.box().
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
Inline text node. Maps to ui.text().
Styled text with multiple spans. Maps to ui.richText().
Icon display from icon registry. Maps to ui.icon().
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 >
Pressable button with design system styling. Maps to ui.button().
Single-line text input. Maps to ui.input().
Multi-line text input. Maps to ui.textarea().
Boolean checkbox control. Maps to ui.checkbox().
Dropdown select menu. Maps to ui.select().
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 >
Root page container. Maps to ui.page().
Titled content panel. Maps to ui.panel().
Elevated card container. Maps to ui.card().
Centered modal dialog. Maps to ui.modal().
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 >
Data table with sorting and selection. Maps to ui.table().
Virtualized scrolling list. Maps to ui.virtualList().
Hierarchical tree view. Maps to ui.tree().
Code editor with syntax highlighting. Maps to ui.codeEditor().
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 >
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()
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
Widget Reference Complete widget catalog