Form
A complete form system built on React Hook Form with Zod validation. The Form component provides context for all form inputs, handles validation, and manages submission state.
Component structure
The Form system uses a compound component pattern:
| Component | Description |
|---|---|
Form | Complete form wrapper (combines Root + Content) |
Form.Root | Context provider only (no DOM element) |
Form.Content | The <form> element |
Form.Field | Field wrapper with label, description, error display |
Form.Input | Text input (used inside Form.Field) |
Form.Textarea | Textarea (used inside Form.Field) |
Form.Checkbox | Checkbox (used inside Form.Field) |
Form.Switch | Toggle switch (used inside Form.Field) |
Form.RadioGroup | Radio button group (used inside Form.Field) |
Form.InputGroup | Input with addons (icons, buttons, text) |
Form.InputGroupInput | Text input inside InputGroup |
Form.InputGroupAddon | Addon container for icons/text |
Form.DatePicker | Date picker (used inside Form.Field) |
Form.SubmitButton | Submit button with automatic loading state |
Basic usage
Use Form.Field to wrap form inputs with label, description, and error handling. The field passes its name and required state to child inputs via context.
<Form onSubmit={handleSubmit}>
<Form.Field name="email" label="Email">
<Form.Input />
</Form.Field>
<Form.Field name="password" label="Password">
<Form.Input type="password" />
</Form.Field>
<Form.SubmitButton>Sign In</Form.SubmitButton>
</Form>Form.Field props
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | — | Field name (required) |
label | ReactNode | — | Label text |
description | ReactNode | — | Helper text below input |
required | boolean | — | Show required indicator and validate |
orientation | 'vertical' | 'horizontal' | 'vertical' | Layout direction |
controlFirst | boolean | — | Put input before label (for checkboxes) |
For advanced layouts (card-style checkboxes, radio cards), see Field.
Form inputs
Form inputs connect to their parent Form.Field context automatically. Add validation with required on the Field.
Horizontal layout
Use orientation="horizontal" to display label and input side by side. For switches and checkboxes, add controlFirst to place the control before the label.
Validation with Zod
Pass a Zod schema to enable validation. Errors display automatically on the corresponding inputs.
import { z } from 'zod'
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Must be at least 8 characters'),
})
<Form schema={schema} onSubmit={handleSubmit}>
<Form.Field name="email" label="Email">
<Form.Input />
</Form.Field>
<Form.Field name="password" label="Password">
<Form.Input type="password" />
</Form.Field>
<Form.SubmitButton>Sign In</Form.SubmitButton>
</Form>Submit button outside form
When the submit button needs to be outside the <form> element (e.g., in a sticky action bar), use Form.Root and Form.Content separately. The submit button automatically connects via the HTML form attribute.
<Form.Root schema={schema} onSubmit={handleSubmit}>
<Form.Content>
<Form.Field name="email" label="Email">
<Form.Input />
</Form.Field>
<Form.Field name="name" label="Name">
<Form.Input />
</Form.Field>
</Form.Content>
<ActionBar>
<Form.SubmitButton>Save Changes</Form.SubmitButton>
</ActionBar>
</Form.Root>Form.Root— Provides form context (react-hook-form) without rendering a DOM elementForm.Content— Renders the<form>element with the connectedidForm.SubmitButton— Uses theformattribute to submit from anywhere withinForm.Root
The standard Form component composes these internally:
<Form schema={schema} onSubmit={handleSubmit}>
<Form.Field name="email" label="Email">
<Form.Input />
</Form.Field>
<Form.SubmitButton>Save</Form.SubmitButton>
</Form>Is equivalent to:
<Form.Root schema={schema} onSubmit={handleSubmit}>
<Form.Content>
<Form.Field name="email" label="Email">
<Form.Input />
</Form.Field>
<Form.SubmitButton>Save</Form.SubmitButton>
</Form.Content>
</Form.Root>External form instance
For advanced control, create the form instance yourself and pass it to Form:
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { email: '' },
})
// Access form state
const isDirty = form.formState.isDirty
<Form form={form} onSubmit={handleSubmit}>
<Form.Field name="email" label="Email">
<Form.Input />
</Form.Field>
<Form.SubmitButton disabled={!isDirty}>Save</Form.SubmitButton>
</Form>