Form Error Summary
The error pattern that actually works with a screen reader: focus the summary, link to the fields, punish late.
- Category
- Patterns
- Added
- 2026-09-21
- Deps
- none
- Updated
- 2026-09-21
This is a viberdy Pro component
The live preview, the props panel and the write-up are open to everyone. The full source, the AI prompt and CLI install are part of Pro.
Props
"use client";
import { useRef, useState } from "react";
type Errors = Record<string, string>;
const FIELDS = [
{ id: "name", label: "Full name", type: "text", hint: "As it appears on your card." },
{ id: "email", label: "Email", type: "email", hint: "" },
{ id: "code", label: "Access code", type: "text", hint: "Six characters, letters and digits." },
];
function validate(values: Record<string, string>): Errors {
const e: Errors = {};
if (!values.name?.trim()) e.name = "Enter your full name.";
// A SHAPE CHECK, not validation. Only sending mail proves an address exists,
// and every clever regex here rejects somebody's real address.
else if (values.name.trim().length < 2) e.name = "Enter your full name, not an initial.";
// ---------------------------------------------------------------
// Full source available with a viberdy Pro subscription.
// https://viberdy.dev/pricing
// ---------------------------------------------------------------
}About this pattern
Form validation is where accessibility and ordinary usability turn out to be the same problem, and the pattern that solves both is the one the GOV.UK Design System arrived at after more testing than anyone else has done. On a failed submit, focus moves to a summary panel rather than to the first invalid field. That is deliberate twice over: a live region whose contents appeared in the same render is announced inconsistently across screen readers and sometimes not at all, while moving focus is reliable everywhere; and jumping to the first bad field tells someone about one problem while hiding the others, when what they need first is how many there are and where. The summary takes a negative tabindex so it can receive focus programmatically without joining the tab order, and each error is a link that explicitly focuses its field, because a fragment link scrolls but does not reliably move focus. The timing rule matters as much as the markup. Validation runs on submit, and after that a field is only re-checked while typing if it is ALREADY in error — so a message clears the moment it is fixed, but nobody is told their email address is wrong while they are on the third character. Each field carries aria-invalid and an aria-describedby that references its hint and its error in that order, and the message is repeated inline as well as in the summary, because the summary is for orientation and the inline copy is what you read while fixing it.
Curator’s note
Focus the summary, not the first bad field. Jumping to a field tells the user about one problem and hides the rest.
Pairs well with
Matched on shared tags and category — the entries most likely to be used alongside this one.
Undo Commit Toast
patternsAn undo window that defers the action instead of reversing it, and pauses when you tab toward the button.
Multi-Step Form Flow
componentsA validated wizard with per-step gating, an editable review step, and a real success state.
Async Action Button
componentsA button that survives a real round trip — a minimum pending duration, a width that never twitches, aria-disabled instead of disabled.