# Form

> How Kanz fields come together into a form: sections with a title and a short why, fields in reading order, one clear action bar, and errors that say how to fix them. No form library built in: it works with your own state, or with react-hook-form and Zod.

Source: https://ritla.app/ui/form

## Installation

Installing from the registry is coming soon. Until then, these are the commands you will run.

```bash
npx ritla add form
```

With the shadcn CLI instead:

```bash
npx shadcn@latest add @ritla/form
```

Dependencies: `@base-ui/react`, `@phosphor-icons/react`

## Usage

```tsx
import { Form, FormActions, FormErrorSummary, FormSection } from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";

<Form onSubmit={save}>
  <FormSection title="Personal details" description="We use these on receipts and transfers.">
    <FormErrorSummary errors={summary} focusKey={submitCount} />
    <Input id="email" label="Email" required type="email" error={errors.email} />
    <FormActions status="Saved 2 minutes ago">
      <Button variant="outline" size="md">Cancel</Button>
      <Button size="md" type="submit">Save changes</Button>
    </FormActions>
  </FormSection>
</Form>
```

## Examples

### Stacked, with errors

`layout="stacked"` for phones, dialogs and narrow columns, and `align="full-width"` puts Save above Cancel at full width. After a failed save, the summary is at the top and each field shows its error in place. `FormOptional` writes "(optional)" after a label; mark the few optional fields, not the many required ones.

Live preview: https://ritla.app/_kanz/preview/ltr/form-stacked

### With react-hook-form and Zod

For teams that already use them. Kanz adds neither: install them yourself (`npm install react-hook-form zod @hookform/resolvers`). `register` gives each field its name, ref and handlers; the schema's messages go to `error` and to the summary, and `submitCount` brings focus back to the summary on every save; `shouldFocusError: false` leaves focus to the summary.

Live preview: https://ritla.app/_kanz/preview/ltr/form-react-hook-form

## API reference

### `Form`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `onSubmit` | `(event) => void` | None | On submit. Check every field here, and show the summary when something needs fixing. |
| `noValidate` | `boolean` | `true` | Turns off the browser's error bubbles, so errors show in the Kanz fields and the summary. |

### `FormSection`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `ReactNode` | None | The section title ("Personal details"). The section is a `<section>` named by it. |
| `description` | `ReactNode` | None | The short why under the title: why you ask for these. |
| `layout` | `"auto" \| "two-column" \| "stacked"` | `"auto"` | Figma's Layout: two columns for desktop settings (the why on one side, the fields on the other), or stacked. `auto` stacks below the lg breakpoint. |

### `FormRow / FormOptional`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children (FormRow)` | `ReactNode` | None | Two fields side by side when there is room (first and last name), stacked on phones. |
| `FormOptional` | `component` | None | Writes "(optional)" in the page language after a label. |

### `FormErrorSummary`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `errors` | `{ id, label, message }[]` | None | One entry per field to fix, in form order. `id` is the field control's id, so the link moves focus there. Nothing renders while the list is empty. |
| `title` | `ReactNode` | None | The heading. Default: "Fix 2 things before you save" in English and «صحّح الحقول التالية:» in Arabic, the count in the page's numerals. |
| `focusKey` | `unknown` | None | Takes focus whenever it changes: pass the submit count, so every failed submit lands here and fixing a field never pulls focus away from it. |
| `autoFocus` | `boolean` | `true` | Takes focus when it appears. |

### `FormActions`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` | None | The buttons in reading order: Cancel (Outline), then Save (Filled, the only filled button). |
| `status` | `ReactNode` | None | The save status at the start of the bar ("Saved 2 minutes ago"), read out politely when it changes. |
| `align` | `"auto" \| "end" \| "full-width"` | `"auto"` | Figma's Align: Save at the end on desktop, or Save above Cancel at full width on phones. `auto` is full width below the sm breakpoint. |

## Accessibility

Keyboard (keys as in left-to-right; arrows swap in right-to-left):

- `Tab`: Through the fields in reading order, then Cancel and Save.
- `Enter`: In a text field, submits the form; on a summary link, moves focus to its field.

ARIA:

- The summary is `role="alert"` and takes focus after a failed submit, so keyboard and screen reader users land on it; each line links to its field.
- Check a field when people leave it, not while they type; the error clears as soon as the value is right.
- Every field has a visible label. The asterisk is decoration: `required` on the field is what announces it.
- An error names the field and the fix ("Enter an address like name@example.com"), never just "Invalid".

## Arabic and RTL notes

Mirrors:

- In two columns: the title and the why on the right, the fields on the left.
- The action bar: Save at the inline end (the left) with Cancel beside it, the save status on the right.
- The summary: the warning glyph on the right, the text after it.

Does not mirror:

- Email, phone and IBAN stay left to right, isolated inside the Arabic page.
- The warning glyph doesn't mirror.

Numerals: The count in the summary's default title follows `numerals`, as does any number you write into messages through `formatNumber`.

Arabic typography: The section title is `body-18` Semibold and the why `label-14`; Arabic lines are taller, so no fixed heights on text.

Mixed-direction text: A summary line is `dir="auto"`: an English message from a server on an Arabic page keeps its order.
