FormField
Binds a label, helper text and error to a control.
The value here is the wiring, not the layout. FormField generates stable ids with `useId` and connects them through `htmlFor` and `aria-describedby` — the part hand-written forms almost always get wrong. Anything you set explicitly on the child wins.
Import
import { FormField } from "@abbainitiative/ui";Also available from the subpath entry @abbainitiative/ui/form-field.
This component carries its own "use client" directive. You can still render it from a Server Component — you simply cannot pass it a function prop, because functions do not serialise across the boundary.
Examples
Label, description and error
Focus the field with a screen reader running: the label, then the description, then the error are announced in order.
<Stack gap={5}>
<FormField label="Email address" description="We only use this to sign you in." required>
<Input type="email" placeholder="you@example.com" />
</FormField>
<FormField label="Username" error="That username is already taken.">
<Input defaultValue="ada" />
</FormField>
</Stack>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label | ReactNode | Visible caption for the control. | |
| description | ReactNode | Helper text describing the expected input. | |
| error | ReactNode | Validation failure. Its presence puts the control into the invalid state. | |
| required | boolean | false | Marks the control required, visually and in its props. |
| disabled | boolean | false | Dims the label and disables the control. |
| children (required) | ReactElement | Exactly one form control. |
Accessibility
- `htmlFor` and the control's `id` are generated together, so clicking the label focuses the field.
- Description and error ids are joined into `aria-describedby` in reading order.
- An error sets `aria-invalid` on the control and renders the message with `role="alert"`.
- Ids come from `useId`, which is stable across server and client, so the association survives hydration.
- An explicit `id` or `aria-describedby` on the child is never overwritten.