Form Error Summary
The error pattern that actually works with a screen reader: focus the summary, link to the fields, punish late.
DOM + CSS0 deps1 file · 7 KB TSX
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
// ---------------------------------------------------------------
}Details
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.
