Components
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.
formPreview
Mirrored is the same English content laid out right to left: it separates a mirroring bug from a translation one.
Installation
npx ritla add formAlso works with shadcn: npx shadcn@latest add @ritla/form
Dependencies it brings: @base-ui/react, @phosphor-icons/react
Installing by hand copies the component's source into your project. The source comes with a Ritla UI license, so the CLI is the short way to the same files.
Usage
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.
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.
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 are written for left-to-right. In right-to-left the arrow keys swap: the one pointing toward the reading direction moves forward.
| Key | Action |
|---|---|
| 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:
requiredon 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 throughformatNumber. - Arabic typography
- The section title is
body-18Semibold and the whylabel-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.