name: rezi-create-screen description: Create a new screen/page for a Rezi TUI application. Use when adding views, pages, or screens to an app. user-invocable: true allowed-tools: Read, Glob, Grep, Edit, Write argument-hint: "[screen-name]" metadata: short-description: Create app screen
When to use
Use this skill when:
- Adding a new screen or page to a Rezi application
- Setting up a view function for an app route
- Scaffolding a new section of a TUI
Source of truth
packages/core/src/widgets/ui.ts— allui.*factory functionspackages/core/src/widgets/composition.ts—defineWidget()and animation hookspackages/core/src/router/— router and route definitionspackages/core/src/keybindings/— keybinding systempackages/create-rezi/templates/starship/— large-screen routing and animation patternsdocs/guide/widget-authoring.md— design system integration patterns
Steps
Create screen file at
src/screens/{screen-name}.ts:import type { VNode } from "@rezi-ui/core"; import { ui } from "@rezi-ui/core"; import type { AppState } from "../types.js"; type ScreenHandlers = Readonly<{ onPrimaryAction: () => void; }>; export function renderMyScreen(_state: AppState, handlers: ScreenHandlers): VNode { return ui.page({ p: 1, gap: 1, header: ui.header({ title: "Screen Title" }), body: ui.column({ gap: 1 }, [ ui.text("Screen content"), ui.actions([ ui.button({ id: "my-screen-action", label: "Go", intent: "primary", onPress: handlers.onPrimaryAction, }), ]), ]), }); }Prefer intent-based button styling (
intent: "primary","secondary","danger","success","warning","link"):ui.button({ id: "action", label: "Go", intent: "primary", onPress: handleAction, })Use
ui.page()orui.appShell()as the screen rootIf the screen needs motion, prefer declarative hooks inside
defineWidget:import { defineWidget, ui, useAnimatedValue, useTransition } from "@rezi-ui/core"; const AnimatedScreen = defineWidget<{ target: number }>((props, ctx) => { const drift = useTransition(ctx, props.target, { duration: 180, easing: "easeOutCubic", }); const energy = useAnimatedValue(ctx, props.target, { mode: "spring", spring: { stiffness: 190, damping: 22, }, }); return ui.box( { width: Math.round(20 + drift), opacity: Math.max(0.35, Math.min(1, energy.value / 100)), transition: { duration: 180, easing: "easeInOutCubic", properties: ["size", "opacity"] }, exitTransition: { duration: 200, easing: "easeInCubic", properties: ["opacity"] }, }, [ui.text("Animated screen")], ); });If using router, add a route definition (see
rezi-routingskill)Add keybindings for screen-specific actions in
src/helpers/keybindings.tsor your route command resolverWire into
src/main.tsvia a view builder or route factory:app.view((state) => renderMyScreen(state, { onPrimaryAction: () => dispatch({ type: "open-my-screen" }), }), );For routed apps, add the screen to
src/screens/index.ts:{ id: "my-screen", title: "My Screen", screen: (_params, context) => renderMyScreen(context.state, { onPrimaryAction: () => dispatch({ type: "open-my-screen" }), }), }
Verification
- Screen renders without errors
- Navigation keybindings work
- State types include any new fields
- For animated screens: transitions retarget smoothly and no timer leaks occur on unmount