Forms
Zen provides a complete form system built on react-hook-form . The Form and FormField components handle validation, error display, and field state management with minimal boilerplate.
How it works
The form system consists of several components that work together:
- Form - Wraps your form and provides react-hook-form context
- FormField - Connects any input component to the form with automatic value binding and validation
- FormButtons - Container for form action buttons
- FormSubmitButton - Submit button with automatic loading and disabled states
- FormResetButton - Reset button that clears the form to default values
Basic usage
Wrap your inputs with FormField components inside a Form. Each FormField needs a name that corresponds to a key in your form data.
Validation
Add validation rules to FormField using the rules prop. This uses react-hook-form’s validation API.
Common validation rules
| Rule | Description |
|---|---|
required | Field must have a value |
min | Minimum value for numbers |
max | Maximum value for numbers |
minLength | Minimum string length |
maxLength | Maximum string length |
pattern | Regex pattern to match |
validate | Custom validation function |
Loading states
FormSubmitButton automatically shows a loading spinner and disables itself while the form is submitting. This happens when your onSubmit handler returns a Promise.
The submit button is automatically disabled when:
- The form has not been modified (
isDirtyis false) - The form has validation errors (
isValidis false) - The form is currently submitting (
isSubmittingis true)
You can override this behavior with the isDisabled prop.
Error handling
Pass an error prop to Form to display an error banner above the form. This is useful for showing server-side validation errors or API failures.
All form inputs
FormField works with all Zen input components. The field automatically binds the value and onChange props.
Dynamic fields with FormFieldArray
Use FormFieldArray when you need to manage a list of fields that can be added or removed dynamically.
Accessing form state
The Form component accepts a render function as children, giving you access to the full react-hook-form API.
<Form defaultValues={{ name: '' }} onSubmit={handleSubmit}>
{({ watch, formState }) => (
<>
<FormField name="name" label="Name">
<TextField />
</FormField>
<Text>Current value: {watch('name')}</Text>
<Text>Form is dirty: {formState.isDirty ? 'Yes' : 'No'}</Text>
<FormButtons>
<FormSubmitButton variant="primary">Submit</FormSubmitButton>
</FormButtons>
</>
)}
</Form>Schema validation
Use the resolver prop to integrate with schema validation libraries like Zod or Yup.
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be at least 8 characters'),
});
function MyForm() {
return (
<Form
resolver={zodResolver(schema)}
onSubmit={handleSubmit}
>
<FormField name="email" label="Email">
<TextField />
</FormField>
<FormField name="password" label="Password">
<PasswordField />
</FormField>
<FormButtons>
<FormSubmitButton variant="primary">Submit</FormSubmitButton>
</FormButtons>
</Form>
);
}Controlled forms
Use the values prop instead of defaultValues when you need to control form values externally. The form will reset when the values change.
function EditUserForm({ user }) {
return (
<Form
values={{ name: user.name, email: user.email }}
onSubmit={handleSubmit}
>
<FormField name="name" label="Name">
<TextField />
</FormField>
<FormField name="email" label="Email">
<TextField />
</FormField>
<FormButtons>
<FormSubmitButton variant="primary">Save</FormSubmitButton>
</FormButtons>
</Form>
);
}Preventing enter key submission
Use preventSubmit to stop the form from submitting when the user presses Enter. Useful for forms where you want explicit button clicks only.
<Form preventSubmit onSubmit={handleSubmit}>
<FormField name="search" label="Search">
<TextField />
</FormField>
<FormButtons>
<FormSubmitButton variant="primary">Search</FormSubmitButton>
</FormButtons>
</Form>Component props
Form
| Name | Type | Description |
|---|---|---|
| defaultValues | object | Initial form values (uncontrolled) |
| values | object | Current form values (controlled) |
| onSubmit | (data) => void | Promise | Called when form is submitted with valid data |
| error | ReactNode | Error | Error message to display above the form |
| preventSubmit | boolean | Prevents form submission on Enter key |
| resolver | Resolver | Validation resolver for schema validation (Zod, Yup, etc.) |
| mode | string | Validation mode: "onSubmit" | "onBlur" | "onChange" | "onTouched" | "all" |
FormField
| Name | Type | Description |
|---|---|---|
| name | string | Field name (required). Must match a key in form values. |
| label | string | Label displayed above the input |
| description | string | Help text displayed below the input |
| rules | RegisterOptions | Validation rules (required, min, max, pattern, validate, etc.) |
FormFieldArray
| Name | Type | Description |
|---|---|---|
| name | string | Field array name (required) |
| label | string | Label displayed above the field array |
| description | string | Help text displayed below the label |
| children | (props) => ReactNode | Render function receiving fields, append, remove, and control |
FormSubmitButton
| Name | Type | Description |
|---|---|---|
| variant | ButtonVariant | Button style variant (default: "primary") |
| isDisabled | boolean | Override automatic disabled state |
| isLoading | boolean | Override automatic loading state |
FormResetButton
| Name | Type | Description |
|---|---|---|
| values | object | Values to reset the form to (default: original defaultValues) |