A box at the top of a form after a failed send, listing every mistake as a link that moves focus to its field.
There is a problem
Fix these so we can send your details:
Options
| Choice | Values | Default | What it does |
|---|---|---|---|
sizesmbk-error-summary--size-<value> | mdsm | md | Small sits closer to its edges with a smaller heading, for a dialog or a narrow form. |
Examples
Usage
Every mistake as a link to its field, worded exactly as the message beside it.
There is a problem
Fix these so we can send your details:
Sizes
Medium for a page’s form; small for a dialog or a narrow form.
There is a problem
Fix these so we can send your details:
There is a problem
Fix these so we can send your details:
A problem with no field
A problem with the whole form is words alone, not a link.
There is a problem
- Your session ended before the form was sent. Your answers are kept: send it again.
- Enter your email address
Focus
The ring it draws round itself when the page moves focus to it.
There is a problem
Fix these so we can send your details:
After a failed send
Send the form empty: the summary appears and takes focus, and each link moves focus to its field.
In a narrow space
Long mistakes wrap; the icon stays on the heading’s line.
There is a problem
Fix these so we can send your details:
Guidelines
Purpose
Lists every mistake in a form at its top, after the form was sent with mistakes, so a reader sees at once how many things need fixing and can go straight to each. Each mistake is a link to its field. The message beside each field still says what is wrong there; the summary gathers those messages in one place.
When to use
- At the top of a form, after it was sent with one mistake or more.
- For a long form, or one that runs over more than one screen, where a reader cannot see every field's message at once.
When not to use
- Before the form is sent. Check fields as they are left, if at all, and show the message beside the field.
- For a problem the reader cannot fix, such as a server that is down. Use an alert or an empty state.
- For a single short field, such as a search box. Its own message is enough.
Usage rules
Do
- Show it only after the form was sent with mistakes, and take it away when they are fixed and the form is sent.
- List the mistakes in the order of the fields.
- Use the same words in the summary and beside the field.
Don't
- Show a summary with no message beside each field.
- Move focus to it while someone is still filling in the form.
- List problems the reader cannot fix.
There is a problem
Say what is wrong and how to fix it, in the words the field shows.
There is a problem
A bare “Invalid” tells the reader something is wrong but not what to do.
Anatomy
- The block, a box on the danger color's subtle surface, inside a hairline edge, with small container corners.
- The icon part, the danger icon, on the heading's first line.
- The content part, holding the text and the list.
- The text part: the heading part ("There is a problem") over the optional intro part, a line such as "Fix these so we can send your details:".
- The list part, one item part per mistake, in the order the fields come in the form.
- The link part, inside an item whose mistake belongs to a field: the mistake's words, underlined, in the danger text color. An item for a problem with the whole form holds its words alone.
Behavior
The summary takes focus as it appears, so the page scrolls to it and a screen reader reads its heading. Send the form again with mistakes still in it and the summary takes focus again: give it a focusKey that changes with each send, such as a count of sends. A summary that shows only after the page reloads takes focus as the page loads.
Pressing a mistake's link brings the field's label into view and moves focus to the field. For a group of choices, such as radio buttons, focus moves to the first choice marked invalid, or the first choice, and the group's question comes into view. Without script, the link goes to the field's id, as any link to a part of a page does.
While it has focus, the summary draws the focus ring round itself, whether focus came from a press, the keyboard or the page.
Accessibility
What it does for you
- The summary is a group named by its heading, and the page moves focus to it, so a screen reader reads "There is a problem" without a live region. It is not a Tab stop: Tab from the summary reaches its first link.
- Each mistake with a field is a link, so it can be reached with Tab and listed with a screen reader's list of links.
- The heading's level follows the page's outline: 2 by default.
- The words carry the meaning; the danger color only backs them up.
What you must do
- Word each mistake exactly as the message beside its field, and say how to fix it: "Enter a date in the future", never "Invalid date".
- Give each field the id its mistake names. Give a group of choices its id on the group (the field group's fieldset), and its choices are reached through it.
- Set the heading's level with
headingLevelto follow the page's outline. - Give it a
focusKeythat changes with each send, so it takes focus again when the form is sent with mistakes still in it.
How to test
- Send the form with mistakes: focus moves to the summary and a screen reader reads its heading.
- Tab from the summary to the first link, then press Enter: focus moves to the field, with its label in view.
- With a screen reader's list of links, each mistake appears as a link.
- With text at 200% and the page 320 pixels wide, the words and links stay whole.
- In a forced-color mode, the summary's focus ring and the links are still visible.
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus from the summary to its first link, then on through the links. |
| Enter | Follows the focused link, moving focus to its field. |
Source
The files the error summary is made of. Its folder on GitHub (opens in a new tab) holds them with its wrapper and its document.
Stylesheet
/* smbk-error-summary — Error summary. Figma "Error summary" 7110:412 (Size × State) and "Error summary item"
* 7110:350 (Type). A box on the danger family's subtle surface, inside a hairline of its edge colour, at the small
* container corners: the danger icon, then the heading over an optional intro line, then the mistakes, each a link
* in the danger text colour, underlined, that moves focus to its field. It is focused by the page after a failed
* send, and draws the focus ring round itself whenever it has focus, from a click as from the keyboard. */
.smbk-error-summary {
/* Always the danger family (the tone recipe). */
--smbk-tone-surface: var(--smbk-color-feedback-danger-surface-subtle);
--smbk-tone-border: var(--smbk-color-feedback-danger-border);
--smbk-tone-text: var(--smbk-color-feedback-danger-text-on-subtle);
--smbk-tone-icon: var(--smbk-color-feedback-danger-icon);
box-sizing: border-box;
display: flex;
align-items: flex-start;
gap: var(--smbk-space-inline-lg);
padding: var(--smbk-space-inset-xl);
background: var(--smbk-tone-surface);
border: var(--smbk-border-width-hairline) solid var(--smbk-tone-border);
border-radius: var(--smbk-radius-container-sm);
color: var(--smbk-tone-text);
}
/* The page moves focus here; the ring shows for that focus too, not only for the keyboard's. */
.smbk-error-summary:focus {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: var(--smbk-focus-ring-offset);
}
/* The icon sits in a box one heading line tall, so it is centred on the heading's first line at either size. */
.smbk-error-summary__icon {
flex: none;
display: flex;
align-items: center;
height: 1lh;
font-family: var(--smbk-typography-subtitle-font-family);
font-size: var(--smbk-typography-subtitle-font-size);
line-height: var(--smbk-typography-subtitle-line-height);
color: var(--smbk-tone-icon);
}
.smbk-error-summary__icon .smbk-icon {
font-size: var(--smbk-size-icon-md);
color: inherit;
}
.smbk-error-summary__content {
flex: 1 1 auto;
min-width: 0;
display: flex;
flex-direction: column;
gap: var(--smbk-space-stack-md);
}
.smbk-error-summary__text {
display: flex;
flex-direction: column;
gap: var(--smbk-space-text-label);
}
.smbk-error-summary__heading {
margin: 0;
color: var(--smbk-tone-text);
font-family: var(--smbk-typography-subtitle-font-family);
font-size: var(--smbk-typography-subtitle-font-size);
font-weight: var(--smbk-typography-subtitle-font-weight);
line-height: var(--smbk-typography-subtitle-line-height);
letter-spacing: var(--smbk-typography-subtitle-letter-spacing);
}
.smbk-error-summary__intro {
margin: 0;
color: var(--smbk-color-text-default);
font-family: var(--smbk-typography-body-md-font-family);
font-size: var(--smbk-typography-body-md-font-size);
font-weight: var(--smbk-typography-body-md-font-weight);
line-height: var(--smbk-typography-body-md-line-height);
letter-spacing: var(--smbk-typography-body-md-letter-spacing);
}
.smbk-error-summary__list {
display: flex;
flex-direction: column;
gap: var(--smbk-space-stack-md);
margin: 0;
padding: 0;
list-style: none;
}
.smbk-error-summary__item {
margin: 0;
color: var(--smbk-tone-text);
font-family: var(--smbk-typography-body-md-font-family);
font-size: var(--smbk-typography-body-md-font-size);
font-weight: var(--smbk-typography-body-md-font-weight);
line-height: var(--smbk-typography-body-md-line-height);
letter-spacing: var(--smbk-typography-body-md-letter-spacing);
}
/* The link keeps the danger text colour and its underline at rest, on hover and when pressed: the summary's colour
is what ties it to the fields' messages. */
.smbk-error-summary__link {
color: inherit;
text-decoration: underline;
border-radius: var(--smbk-radius-control-sm);
-webkit-tap-highlight-color: transparent;
}
.smbk-error-summary__link:focus-visible {
outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
outline-offset: var(--smbk-focus-ring-offset);
}
/* ---- small: closer to its edges, the heading in Label LG ---- */
.smbk-error-summary--size-sm {
padding: var(--smbk-space-inset-lg);
}
.smbk-error-summary--size-sm .smbk-error-summary__icon {
font-family: var(--smbk-typography-label-lg-font-family);
font-size: var(--smbk-typography-label-lg-font-size);
line-height: var(--smbk-typography-label-lg-line-height);
}
.smbk-error-summary--size-sm .smbk-error-summary__heading {
font-family: var(--smbk-typography-label-lg-font-family);
font-size: var(--smbk-typography-label-lg-font-size);
font-weight: var(--smbk-typography-label-lg-font-weight);
line-height: var(--smbk-typography-label-lg-line-height);
letter-spacing: var(--smbk-typography-label-lg-letter-spacing);
}
@media (forced-colors: active) {
.smbk-error-summary:focus,
.smbk-error-summary__link:focus-visible {
outline-color: Highlight;
}
}
/* ---- touch: each link takes an invisible press area ----
On a touch screen an invisible area, centred on each link, takes the press out to the comfortable target in each
direction it falls short of it, and between two links only halfway into the list's gap, so the links stay one
press each. The links, their focus ring and their places stay as drawn. */
@media (pointer: coarse) {
.smbk-error-summary__link {
position: relative;
}
.smbk-error-summary__link::after {
content: '';
position: absolute;
inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
}
/* Halfway between two links: half the list's gap and half of what the line keeps round the link's own box. */
.smbk-error-summary__item:has(.smbk-error-summary__link) + .smbk-error-summary__item .smbk-error-summary__link::after {
inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (var(--smbk-space-stack-md) + 1lh - 100%) / -2);
}
.smbk-error-summary__item:has(+ .smbk-error-summary__item .smbk-error-summary__link) .smbk-error-summary__link::after {
inset-block-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), (var(--smbk-space-stack-md) + 1lh - 100%) / -2);
}
}Tokens it reads
| Token and CSS | Value | Preview | Foundation |
|---|---|---|---|
| color.feedback.danger.surface-subtle--smbk-color-feedback-danger-surface-subtle | light: #fee5e1dark: #4b130echanges with theme | Color | |
| color.feedback.danger.border--smbk-color-feedback-danger-border | #f34336 | Color | |
| color.feedback.danger.text-on-subtle--smbk-color-feedback-danger-text-on-subtle | light: #a51d17dark: #febdb2changes with theme | Color | |
| color.feedback.danger.icon--smbk-color-feedback-danger-icon | light: #cc0d0ddark: #fe7d6dchanges with theme | Color | |
| space.inline.lg--smbk-space-inline-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| space.inset.xl--smbk-space-inset-xl | comfortable: 16pxcompact: 12pxchanges with density | Spacing | |
| border-width.hairline--smbk-border-width-hairline | 1px | Border width | |
| radius.container.sm--smbk-radius-container-sm | 8px | Radius | |
| focus.ring.width--smbk-focus-ring-width | 2px | Focus | |
| color.border.focus--smbk-color-border-focus | light: #cc0d0ddark: #f34336changes with theme | Color | |
| focus.ring.offset--smbk-focus-ring-offset | 2px | Focus | |
| typography.subtitle--smbk-typography-subtitle-font-family--smbk-typography-subtitle-font-size--smbk-typography-subtitle-font-weight--smbk-typography-subtitle-line-height--smbk-typography-subtitle-letter-spacing--smbk-typography-subtitle-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 20pxfontWeight: 400lineHeight: 1.4letterSpacing: 0px | The quick brown fox | Typography |
| size.icon.md--smbk-size-icon-md | 20px | Size | |
| space.stack.md--smbk-space-stack-md | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| space.text.label--smbk-space-text-label | 0px | Spacing | |
| color.text.default--smbk-color-text-default | light: #232527dark: #e7ebf0changes with theme | Color | |
| typography.body-md--smbk-typography-body-md-font-family--smbk-typography-body-md-font-size--smbk-typography-body-md-font-weight--smbk-typography-body-md-line-height--smbk-typography-body-md-letter-spacing--smbk-typography-body-md-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 400lineHeight: 1.5letterSpacing: 0px | The quick brown fox | Typography |
| radius.control.sm--smbk-radius-control-sm | 4px | Radius | |
| space.inset.lg--smbk-space-inset-lg | comfortable: 12pxcompact: 8pxchanges with density | Spacing | |
| typography.label-lg--smbk-typography-label-lg-font-family--smbk-typography-label-lg-font-size--smbk-typography-label-lg-font-weight--smbk-typography-label-lg-line-height--smbk-typography-label-lg-letter-spacing--smbk-typography-label-lg-text-transform | fontFamily: Geist, Geist Fallback, system-ui, sans-seriffontSize: 16pxfontWeight: 500lineHeight: 1.4letterSpacing: 0px | The quick brown fox | Typography |
| size.target.comfortable--smbk-size-target-comfortable | 44px | Size |
Manifest
{
"name": "error-summary",
"block": "smbk-error-summary",
"summary": "A box at the top of a form after a failed send, listing every mistake as a link that moves focus to its field.",
"adapt": "css",
"axes": {
"size": {
"values": [
"md",
"sm"
],
"default": "md",
"description": "Small sits closer to its edges with a smaller heading, for a dialog or a narrow form."
}
},
"flags": {},
"parts": [
"icon",
"content",
"text",
"heading",
"intro",
"list",
"item",
"link"
],
"figmaNodeId": "7110:412"
}