Documentation Index Fetch the complete documentation index at: https://mintlify.com/RtlZeroMemory/Rezi/llms.txt
Use this file to discover all available pages before exploring further.
This tutorial walks you through creating a simple counter application using Rezi’s ui.* factory functions. You’ll learn the basics of state management, widget composition, and event handling.
Prerequisites
Before starting, make sure you have:
Node.js 18+ or Bun 1.3+ installed
A terminal with 256-color or true-color support
Step 1: Scaffold a New Project
The fastest way to start is using create-rezi:
npm create rezi counter-app
cd counter-app
When prompted:
Select minimal template (or press Enter for the default)
Wait for dependencies to install
Step 2: Understanding the Project Structure
The minimal template creates this structure:
counter-app/
├── src/
│ ├── main.ts # App entry point
│ ├── types.ts # State and action types
│ ├── theme.ts # Theme configuration
│ ├── screens/
│ │ └── main-screen.ts # View function
│ ├── helpers/
│ │ ├── state.ts # State reducer
│ │ └── keybindings.ts # Key commands
│ └── __tests__/
│ ├── render.test.ts
│ ├── reducer.test.ts
│ └── keybindings.test.ts
├── package.json
└── tsconfig.json
Step 3: Build a Counter App
Let’s modify the generated code to create a counter. Open src/types.ts and define your state:
export type AppState = {
count : number ;
themeName : "dark" | "light" | "nord" | "dracula" | "dimmed" | "high-contrast" ;
};
export type AppAction =
| { type : "increment" }
| { type : "decrement" }
| { type : "reset" }
| { type : "cycle-theme" };
Step 4: Create the Reducer
Open src/helpers/state.ts and implement the state logic:
import type { AppState , AppAction } from "../types.js" ;
export function createInitialState () : AppState {
return {
count: 0 ,
themeName: "dark" ,
};
}
const THEMES = [ "dark" , "light" , "nord" , "dracula" , "dimmed" , "high-contrast" ] as const ;
export function reduceAppState ( state : AppState , action : AppAction ) : AppState {
switch ( action . type ) {
case "increment" :
return { ... state , count: state . count + 1 };
case "decrement" :
return { ... state , count: state . count - 1 };
case "reset" :
return { ... state , count: 0 };
case "cycle-theme" : {
const currentIndex = THEMES . indexOf ( state . themeName );
const nextIndex = ( currentIndex + 1 ) % THEMES . length ;
return { ... state , themeName: THEMES [ nextIndex ] };
}
default :
return state ;
}
}
Step 5: Create the View
Open src/screens/main-screen.ts and build your UI:
src/screens/main-screen.ts
import { ui } from "@rezi-ui/core" ;
import type { AppState } from "../types.js" ;
type Actions = {
onIncrement : () => void ;
onDecrement : () => void ;
onReset : () => void ;
onCycleTheme : () => void ;
};
export function renderMainScreen ( state : AppState , actions : Actions ) {
return ui . page ({
p: 1 ,
gap: 1 ,
header: ui . header ({
title: "Counter App" ,
subtitle: "Built with Rezi" ,
}),
body: ui . column ({ gap: 2 }, [
// Counter display
ui . panel ( "Count" , [
ui . row ({ gap: 1 , items: "center" }, [
ui . text ( String ( state . count ), { variant: "heading" }),
ui . spacer ({ flex: 1 }),
ui . badge ( state . count > 0 ? "positive" : "neutral" , String ( state . count )),
]),
]),
// Controls
ui . panel ( "Controls" , [
ui . actions ([
ui . button ({
id: "dec" ,
label: "Decrement (-)" ,
intent: "secondary" ,
onPress: actions . onDecrement ,
}),
ui . button ({
id: "reset" ,
label: "Reset (R)" ,
onPress: actions . onReset ,
}),
ui . button ({
id: "inc" ,
label: "Increment (+)" ,
intent: "primary" ,
onPress: actions . onIncrement ,
}),
]),
]),
// Info
ui . callout ( "info" , [
ui . text ( "Press + to increment, - to decrement, R to reset." ),
ui . text ( "Press T to cycle themes. Press Q to quit." ),
]),
]),
});
}
Step 6: Wire Up the App
Open src/main.ts and connect everything:
import { createNodeApp } from "@rezi-ui/node" ;
import { exit } from "node:process" ;
import { createInitialState , reduceAppState } from "./helpers/state.js" ;
import { renderMainScreen } from "./screens/main-screen.js" ;
import { themeSpec } from "./theme.js" ;
import type { AppState , AppAction } from "./types.js" ;
const initialState = createInitialState ();
const app = createNodeApp ({
initialState ,
config: { fpsCap: 30 },
theme: themeSpec ( initialState . themeName ). theme ,
});
function dispatch ( action : AppAction ) : void {
let themeChanged = false ;
let nextThemeName = initialState . themeName ;
app . update (( previous ) => {
const next = reduceAppState ( previous , action );
if ( next . themeName !== previous . themeName ) {
nextThemeName = next . themeName ;
themeChanged = true ;
}
return next ;
});
if ( themeChanged ) {
app . setTheme ( themeSpec ( nextThemeName ). theme );
}
}
app . view (( state : AppState ) =>
renderMainScreen ( state , {
onIncrement : () => dispatch ({ type: "increment" }),
onDecrement : () => dispatch ({ type: "decrement" }),
onReset : () => dispatch ({ type: "reset" }),
onCycleTheme : () => dispatch ({ type: "cycle-theme" }),
}),
);
app . keys ({
q : () => app . stop (),
"+" : () => dispatch ({ type: "increment" }),
"shift+=" : () => dispatch ({ type: "increment" }),
"-" : () => dispatch ({ type: "decrement" }),
r : () => dispatch ({ type: "reset" }),
t : () => dispatch ({ type: "cycle-theme" }),
});
process . once ( "SIGINT" , () => app . stop ());
process . once ( "SIGTERM" , () => app . stop ());
await app . start ();
exit ( 0 );
Step 7: Run Your App
You should see a counter interface with:
A count display with a badge
Three buttons for increment, decrement, and reset
Help text with keyboard shortcuts
Theme cycling support
Keyboard shortcuts:
+ — Increment
- — Decrement
R — Reset
T — Cycle themes
Q — Quit
Understanding the Code
State Management
Rezi uses a simple reducer pattern:
State type — AppState defines what data your app holds
Actions — AppAction union defines all possible state changes
Reducer — reduceAppState() applies actions to produce new state
Update — app.update() triggers a state transition and re-render
Widgets are composed using factory functions from ui.*:
ui.page() — Root container with padding and layout
ui.header() — Title bar with subtitle
ui.panel() — Labeled content section
ui.button() — Interactive button with intent styling
ui.callout() — Info/warning/error message box
All widgets accept style props (p, gap, flex, etc.) and semantic props (variant, intent, etc.).
Event Handling
Events are handled via callbacks:
Widget callbacks — onPress, onInput, onChange, etc.
Global keybindings — app.keys() for keyboard shortcuts
Lifecycle hooks — app.start(), app.stop(), app.dispose()
Using JSX (Optional)
You can also build the same counter with JSX:
src/screens/main-screen.tsx
import { Page , Header , Column , Panel , Row , Text , Spacer , Badge , Button , Actions , Callout } from "@rezi-ui/jsx" ;
import type { AppState } from "../types.js" ;
type ActionsType = {
onIncrement : () => void ;
onDecrement : () => void ;
onReset : () => void ;
onCycleTheme : () => void ;
};
export function renderMainScreen ( state : AppState , actions : ActionsType ) {
return (
< Page
p = { 1 }
gap = { 1 }
header = { < Header title = "Counter App" subtitle = "Built with Rezi" /> }
body = {
< Column gap = { 2 } >
< Panel title = "Count" >
< Row gap = { 1 } items = "center" >
< Text variant = "heading" > { String ( state . count ) } </ Text >
< Spacer flex = { 1 } />
< Badge variant = { state . count > 0 ? "positive" : "neutral" } >
{ String ( state . count ) }
</ Badge >
</ Row >
</ Panel >
< Panel title = "Controls" >
< Actions >
< Button id = "dec" label = "Decrement (-)" intent = "secondary" onPress = { actions . onDecrement } />
< Button id = "reset" label = "Reset (R)" onPress = { actions . onReset } />
< Button id = "inc" label = "Increment (+)" intent = "primary" onPress = { actions . onIncrement } />
</ Actions >
</ Panel >
< Callout variant = "info" >
< Text > Press + to increment, - to decrement, R to reset. </ Text >
< Text > Press T to cycle themes. Press Q to quit. </ Text >
</ Callout >
</ Column >
}
/>
);
}
See JSX Documentation for setup instructions.
Next Steps
Explore Widgets Browse the complete widget catalog
Learn Concepts Understand Rezi’s architecture and patterns
Styling Guide Master layout, theming, and design tokens
Example Templates Explore advanced templates and patterns