Examples
Settings page
A form that validates on submit, moves focus to the first problem, and saves with a toast.
View as Markdown for coding assistants, or see llms.txt.
An account settings screen: three text fields, a group of notification toggles and a Save button.
<form onSubmit={submit} noValidate aria-label="Account settings" className="max-w-xl">
<Card>
<CardHeader>
<CardTitle as="h2">Account</CardTitle>
<CardDescription>How we address you and where we send things.</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-5">
{failed.length > 0 && (
<Alert variant="destructive">
<AlertTitle>
{failed.length === 1
? "One thing needs fixing"
: `${failed.length} things need fixing`}
</AlertTitle>
<AlertDescription>Each message is next to its field.</AlertDescription>
</Alert>
)}
{field("name", "Full name", { autoComplete: "name" })}
{field("email", "Email", { type: "email", autoComplete: "email" })}
{field("website", "Website (optional)", { type: "url", autoComplete: "url" })}
<Separator />
<fieldset className="m-0 flex flex-col gap-3 border-0 p-0">
<legend className="mb-1 text-sm font-medium">Email me about</legend>
{NOTIFICATIONS.map((item) => (
<Toggle
key={item.id}
variant="outline"
defaultPressed={item.on}
className="justify-start"
>
{item.label}
</Toggle>
))}
</fieldset>
</CardContent>
<CardFooter className="gap-2">
<Button type="submit" loading={saving} loadingText="Saving">
Save changes
</Button>
<Button type="reset" variant="ghost" onClick={() => setErrors({})}>
Cancel
</Button>
</CardFooter>
</Card>
</form>What it shows
- Validation on submit. Nothing is flagged while someone is still typing. Pressing Save checks every field, shows all the problems at once and moves focus to the first invalid field.
- Each message is tied to its field. An invalid Input gets
aria-invalid="true"andaria-describedbypointing at its message, so a screen reader reads the problem with the field. A summary Alert says how many fields need fixing. - A saving state that cannot be double-clicked. The Button uses
loading, which keeps keyboard focus and ignores a second activation, andtoast.promisereports the result without moving focus. - A labelled group. The notification choices are in a
fieldsetwith alegend, so they are announced as one group.
Try it
- Clear the name and press Save: focus lands on the name field and its message is read out.
- Type
aroha@in the email and press Save: the email is flagged and the name is not. - Fix both and press Save: the button shows a spinner, then a toast confirms.
Limits
The notification toggles keep their own state and are not read on save; in a real form, hold their values in state next to the text fields. The save is a timer standing in for a request.