Form
Form primitives connect react-hook-form state to labels, descriptions, and inline error messages, and wire aria-invalid / aria-describedby for you. Validate with a zod schema at module scope so the rules are testable without rendering anything.
Import
tsx
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@rogo-technologies/ui/form";
import { zodFormResolver } from "@rogo-technologies/ui/zod-form-resolver";react-hook-form, @hookform/resolvers, and zod are optional peer dependencies, so install them in the consuming app (pinned to 7.71.0 / 5.4.0 / 4.4.3).
Examples
Validated form
Submit the form empty to see each FormMessage render its zod message, with the offending control marked aria-invalid and pointed at the message via aria-describedby.
Usage
tsx
const TeamSchema = z.object({
name: z.string().trim().min(2, "Use at least 2 characters so the team is searchable."),
});
type TeamForm = z.infer<typeof TeamSchema>;
function CreateTeamForm() {
const form = useForm<TeamForm>({
resolver: zodFormResolver<TeamForm>(TeamSchema),
defaultValues: { name: "" },
});
return (
<Form {...form}>
<form id="create-team" noValidate onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Team name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
<Button type="submit" form="create-team">
Save
</Button>
</Form>
);
}Parts
| Part | Renders | Notes |
|---|---|---|
Form | - | FormProvider from react-hook-form. Spread the useForm return into it. |
FormField | - | Controller plus the field-name context the other parts read. |
FormItem | div | Generates the id shared by label, control, description, and message. |
FormLabel | label | htmlFor the control; turns destructive when the field has an error. |
FormControl | slot | Clones its child, adding id, aria-invalid, and aria-describedby. One child only. |
FormDescription | p | Help text, always referenced by aria-describedby. |
FormMessage | p | The field's error message, or its children when there is no error. Renders nothing when both are empty. |
useFormField() exposes the same ids and field state for a control that can't be wrapped in FormControl.
Guidelines
- Keep the submit button outside
<form>and target it withform="<id>"when the footer is sticky and the fields scroll, since a button inside the scroll container scrolls away with them. - Search and filter inputs inside a form get
form=""so pressing Enter in them can't submit the enclosing form. reset()in an effect when the query lands, not indefaultValues, when the form is seeded from server data, sincedefaultValuesis captured once and a late-arriving response would otherwise leave the fields empty.noValidateon the<form>so zod owns the messages rather than the browser's native bubbles.