Forms and feedback
Render accessible field errors
Associate validation messages with their controls and expose invalid state without relying on red borders or placeholder text.
8 minute lesson
Validation is only useful if the user can understand what went wrong. A red border is not enough.
The Field component can connect the input to its error. We also add aria-invalid, so assistive technology can expose the invalid state.
<Controller
name='name'
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>Project name</FieldLabel>
<Input
{...field}
id={field.name}
aria-invalid={fieldState.invalid}
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)
/>
Write errors that help the user fix the value. “Use at least 3 characters” is much better than “Invalid input”.
Give the message a stable ID and include it in the control’s aria-describedby together with any existing help text. This preserves the relationship in the accessibility tree instead of relying on visual proximity. Keep the label visible and never use color as the only signal.
On a short form, leaving focus on submit and announcing a summary may be enough. On a long form, focus a linked error summary after a failed submission so the user can jump to each field. Avoid moving focus on every client-side validation event, which interrupts typing.
Submit the empty form and inspect name, invalid state, description, and message in the accessibility tree. Navigate to every error without a mouse, then repeat with colors disabled or high contrast enabled. The problem and recovery instruction should remain clear.
Lesson completed