--- name: rezi-forms description: Set up form input handling with validation and submission. Use when building forms with inputs, selects, checkboxes, etc. user-invocable: true allowed-tools: Read, Glob, Grep, Edit, Write argument-hint: "[form-name]" metadata: short-description: Set up forms --- ## When to use Use this skill when: - Building forms with multiple inputs - Need field validation and error display - Handling form submission with loading states ## Source of truth - `packages/core/src/forms/useForm.ts` — `useForm()` hook - `packages/core/src/widgets/types.ts` — `InputProps`, `FieldProps`, `CheckboxProps`, `SelectProps` - `packages/core/src/widgets/ui.ts` — `ui.input()`, `ui.field()`, `ui.checkbox()`, `ui.select()` - `packages/core/src/ui/recipes.ts` — `recipe.input()`, `recipe.button()`, `recipe.select()` for design-system styling ## Steps 1. **Use the `useForm()` hook** inside a `defineWidget`: ```typescript import { defineWidget, ui, useForm } from "@rezi-ui/core"; const MyForm = defineWidget<{ onSubmit: (v: Values) => void }>((props, ctx) => { const form = useForm(ctx, { initialValues: { name: "", email: "" }, validate: (values) => { const errors: Record = {}; if (!values.name) errors.name = "Required"; if (!values.email.includes("@")) errors.email = "Invalid email"; return errors; }, onSubmit: async (values) => props.onSubmit(values), }); return ui.column({ gap: 1 }, [ ui.field({ label: "Name", error: form.errors.name, children: ui.input({ id: "field-name", ...form.bind("name") }), }), ui.field({ label: "Email", error: form.errors.email, children: ui.input({ id: "field-email", ...form.bind("email") }), }), ui.actions([ ui.button({ id: "cancel", label: "Cancel", intent: "secondary", onPress: () => {} }), ui.button({ id: "save", label: "Save", intent: "primary", onPress: form.handleSubmit }), ]), ]); }, { name: "MyForm" }); ``` 2. **Bind fields** using `form.bind("fieldName")` — returns object-form input props (`value`, `onInput`, `onBlur`, `disabled`) 3. **Use `ui.field()`** to wrap inputs with labels and error display 4. **Access form state** via `form.errors`, `form.touched`, `form.dirty`, `form.submitting` 5. **Use success/warning for non-submit side actions**: ```typescript ui.button({ id: "approve", label: "Approve", intent: "success", onPress: handleApprove }); ui.button({ id: "review", label: "Needs Review", intent: "warning", onPress: handleReview }); ``` ## Design system note Inputs are recipe-styled by default when semantic color tokens are available (for example via a `ThemeDefinition` preset). Manual overrides merge on top via the same `mergeTextStyle(baseStyle, ownStyle)` pattern used by the renderer. Use manual `style` only for targeted overrides (merged on top of recipe output). For button visuals, use `intent` (`primary`, `secondary`, `danger`, `success`, `warning`, `link`) in app code. Do not use `dsVariant`/`dsTone` in app-level form code. ## Verification - Validation errors display next to fields - Submission works and calls `onSubmit` - Touched/dirty state tracked correctly