Error summary
StableA “There is a problem” box at the top of a form that takes focus after a failed submit and links each message to its field.
For forms with more than a field or two, such as settings. It sits above everything else in the form, uses the same words as the message under each field, and shows even for a single error. Change focusKey on every failed submit and focus moves to it, so screen readers start there.
After a failed save
import { useId, useState, type FormEvent } from 'react'import { ErrorSummary, checkEmail, checkName, type ErrorSummaryItem } from '@rchursin/product/forms'import { Button, Field, Select, TextInput, useToast } from '@rchursin/product/ui'
type Values = { name: string; email: string; team: string }
function validate(v: Values, id: string): ErrorSummaryItem[] { const errors: ErrorSummaryItem[] = [] const name = checkName(v.name) const email = checkEmail(v.email) if (name) errors.push({ id: `${id}-name`, message: name }) if (email) errors.push({ id: `${id}-email`, message: email }) if (!v.team) errors.push({ id: `${id}-team`, message: 'Choose your team' }) return errors}
const START: Values = { name: '', email: 'ana@example', team: '' }
/** * Shown as it looks right after a failed save. Each message links to its field (same words asUsage
import { ErrorSummary } from '@rchursin/product/forms'<ErrorSummary errors={[{ id: 'email', message: 'Enter your email address' }]} focusKey={failedSubmits}/>API
Error summary props
| Prop | Type | Default | Description |
|---|---|---|---|
errorsrequired | ErrorSummaryItem[] | – | One per problem, in form order. Empty renders nothing. |
title | string | 'There is a problem' | The heading. |
focusKey | number | – | Change it (a failed-submit counter) to move focus to the summary. 0 never moves focus, so nothing is taken on page load. |
headingLevel | 2 | 3 | 4 | 2 | The heading level, to fit the page’s outline. |
className | string | – | Classes on the box. |
ErrorSummaryItem
| Prop | Type | Default | Description |
|---|---|---|---|
idrequired | string | – | The id of the field to focus: the input, or the button of a file picker. |
messagerequired | string | – | Exactly the words shown under that field. |
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Tab | From the summary (where focus lands after a failed submit), to each message link in turn. |
| Enter | On a link: scrolls the field’s label into view and puts focus in the field. |
- Each time focusKey changes and there are errors, focus moves to the box, which is named by its heading, so screen readers read it first.
- Its contents are a role="alert", so they are also announced when they appear.
- Each message is a link to its field. Following it scrolls the field’s label into view (smoothly only when motion is allowed) and focuses the field, so the question is visible, not just the box.
- The box is marked by a heavy outline and an alert icon, never by colour.
Guidelines
Do
- Use the same words in the summary and under the field.
- Put it first in the form, above every field.
- Change focusKey on each failed submit, not on each keystroke.
Don’t
- Don’t use it for a one- or two-field form like sign-in; focus the first invalid field instead.
- Don’t disable the submit button to stop errors; let the summary explain.
- Don’t word errors as blame: “Enter your email address”, not “Invalid input”.
In use
Related
- FieldLabel above, hint under it, error in words between the hint and the control, with the ids and aria wiring done for you.
- Form toolkituseForm for values, errors and validation timing, plus the checks and GOV.UK-style messages every form in the kit shares.
- Account settingsThe in-app settings form: profile with a picture and time zone, a password change, passkeys and sessions, with an error summary on save.
- ToastShort past-tense results with at most one action, like “Run archived · Undo”. Read out politely; they wait while hovered or focused.
Source
The component’s own files, exactly as they run on this site.
/** * Error summary (GOV.UK pattern) for forms with more than a field or two, such as settings. * * <ErrorSummary errors={[{ id: 'email', message: 'Enter your email address' }]} focusKey={submits} /> * * - Sits at the top of the form, above everything else, with the heading "There is a problem". * - Each message links to its field; following a link scrolls the field's label into view and * focuses the field. The wording is identical to the inline message. * - Focus moves to the summary each time `focusKey` changes (each failed submit), so screen * readers read it first. Shown even for a single error. * - Auth forms skip it: they focus the first invalid field instead. */import { useEffect, useId, useRef, type MouseEvent } from "react"import { cx } from "../../components/cx"import { Icon } from "../icons/Icon"
export interface ErrorSummaryItem { /** id of the field to focus (the input, or the button for a file picker) */ id: string message: string