React Hook Form
Build forms with React Hook Form and Zod validation.
This guide shows how to build forms using React Hook Form with Kanpeki's Field component and Zod validation.
Demo
Approach
React Hook Form provides performant, uncontrolled form state management. We combine it with:
Fieldcomponents for layout and accessibilityTextFieldfor automatic ID wiring between label, input, description, and errorControllerfor wiring controlled Kanpeki inputszodResolverfrom@hookform/resolversfor schema validation
Anatomy
TextField is Kanpeki's React Aria behavior adapter. It automatically wires Field.Label, Field.Description, and Field.Error via aria-labelledby and aria-describedby using unique generated IDs. See the forms architecture for more.
isInvalid drives error styling. Destructuring ref and disabled from the field lets you pass ref to <Input /> and forward isDisabled to <TextField />, while spreading the rest wires name, value, onChange, and onBlur automatically.
Setup
Install dependencies
Create a schema
Setup the form
Use useForm with zodResolver:
Always provide defaultValues for every field used with <Controller />. Without it, a field starts as undefined (uncontrolled) and becomes a string on first change (controlled), triggering: "A component is changing an uncontrolled input to be controlled."
Validation
Validation Modes
React Hook Form validates on submit by default. Configure with the mode option:
Displaying Errors
Use <Field.Error /> with the errors prop. Pass [fieldState.error] directly — Field.Error ignores undefined entries so it renders nothing when the field is valid:
Field Types
Input
Textarea
Select
Pass <Select.Root /> as the render prop on <Field.Root />. Use value and onChange for RHF wiring:
Checkbox
For checkbox groups, pass <Checkbox.Provider /> as the render prop on each <Field.Root /> and toggle array items on change:
Switch
Switch.Root is a standalone RAC component — pass name, isSelected, and onChange directly. No TextField wrapper is needed since a switch has no text input:
Submit Button State
Disable the submit button while submitting using isSubmitting from formState:
Resetting the Form
Use form.reset() to restore default values:
Array Fields
React Hook Form supports dynamic array fields with useFieldArray:
Array Helpers
Linked Fields
When one field's validation depends on another (e.g. confirm password), use Zod's .refine() at the schema level to cross-validate:
The path option routes the error to the correct field. React Hook Form surfaces it via fieldState.error on the confirmPassword controller.
On This Page