Forms & Validation
Problems are caught before submission and shown inline at the fields — not collected in a summary box, not thrown as toasts. Submit the empty form to see it.
How this works
Every control just has a name — no controlled state, no error threading. The Form runs browser constraint validation first (required, type="email"…), then the zod-backed validate functions, and renders each problem under its own field. After the first attempt, errors clear live as you fix them.
Snippet
const email = z.string().email('Enter a valid email address.')
const zodMessage = (schema, value) => schema.safeParse(value).error?.issues[0]?.message
<Form
validate={{ email: value => zodMessage(email, value) }}
onSubmit={values => api.contact(values)}
>
<TextInput name="email" label="Email" type="email" required />
<Checkbox name="agree" label="I agree to the terms" required />
<Button type="submit">Submit</Button>
</Form>Full contract — focus management, cross-field rules, custom controls — on the Form page.