Validation summary

.validation-summary

Validation summary — the list of errors shown at the top of a form after a submit was rejected, where every item is a link to the field it describes.

How this differs from what it looks like

Not alert

A validation summary is a list of errors with links to the fields that produced them, shown once at the top of a form after a failed submit, and focused so the user lands on it. An alert is any message at all. The difference that matters is the links: an error message that only describes what went wrong leaves the user to find six fields in a long form by hand, and the summary exists precisely so that "Enter an email address" is a link that puts the caret in the email field. If your message has no links to fields, it is an alert; if it does, this component also handles the focus move, which is the part that is otherwise always missed.

Examples

After a failed submit

View “After a failed submit” as
<div class="validation-summary" id="signup-errors" tabindex="-1">
  <h2 class="validation-summary-title">There are 3 problems with this form</h2>
  <ul>
    <li><a class="validation-summary-link" href="#email">Enter an email address in the format name@example.com</a></li>
    <li><a class="validation-summary-link" href="#password">Your password must be at least 12 characters long</a></li>
    <li><a class="validation-summary-link" href="#postcode">Enter a postcode, for example SW1A 1AA</a></li>
  </ul>
</div>

It only helps if focus moves to it, and that needs one line of script the component cannot supply: tabindex="-1" is here, .focus() after render is yours.

Each item links to the field it names, which is the entire value of the pattern.

Tokens 17

Level 2, declared on .validation-summary itself. Set any of them on that selector to restyle this component without touching the skin.

Level 2 tokens declared by validation-summary
TokenDefault
--validation-summary-icon-colorvar(--validation-summary-accent)
--validation-summary-icon-size1.25rem
--validation-summary-item-gapvar(--space-2)
--validation-summary-link-colorvar(--color-on-surface)
--validation-summary-link-font-sizevar(--font-size-sm)
--validation-summary-title-colorvar(--color-on-surface)
--validation-summary-title-font-familyvar(--font-primary)
--validation-summary-title-font-sizevar(--font-size-base)
--validation-summary-title-font-weightvar(--font-weight-semibold)
--validation-summary-accentvar(--color-danger)
--validation-summary-accent-size3px
--validation-summary-bgvar(--color-danger-surface)
--validation-summary-colorvar(--color-on-surface)
--validation-summary-gapvar(--space-3)
--validation-summary-padding-blockvar(--space-4)
--validation-summary-padding-inlinevar(--space-4)
--validation-summary-radiusvar(--radius-box)

Variants and states

Variants

None. It is one shape, and the page does the rest.

Inside it

  • .validation-summary-link
  • .validation-summary-title

State it reads

Read from the platform, never mirrored into a class that could disagree with it.

  • :focus
  • :hover

Before you ship it

What you have to do 4 requirements

  1. Move focus to the container, after it is in the DOM and laid out. A summary nobody is sent to is decoration; there is no CSS property, HTML attribute or server redirect that does this.
  2. Use tabindex="-1", not 0. The summary is a place you are sent, not a stop on the way through the form.
  3. Do not add role="alert" as well. Focus already reads the element out; a live region on top of it announces the whole list twice.
  4. Point each item at its field's id, and give the heading the count.

src/css/components/validation-summary.css · npx mostlycss add validation-summary