Skip to content

Error summary

One list at the top of a failed form, each entry linking to the field that failed.

Preview

Code

The three render the same class names, the same ARIA and the same data attributes. That is checked when this site builds: a class in any of these samples that neither the markup contract nor the shipped stylesheet defines fails the build.

<div class="wsu-error-summary" tabindex="-1" role="alert">
  <h2>There is a problem</h2>
  <ul class="wsu-error-summary__list">
    <li><a href="#course">Enter a course number</a></li>
    <li><a href="#reason">Tell us why you need this override</a></li>
  </ul>
</div>

Why it works this way

  • On a long form, per-field errors alone mean a keyboard or screen reader user has to walk the whole form to find out what went wrong. The summary says it once, at the top, with a link straight to each control.
  • It takes focus when it appears. That is what makes the errors reach a screen reader user who has just submitted, without needing an assertive live region that would interrupt them mid-sentence.
  • When autofocus is turned off it falls back to role="alert", so the errors are still announced.
  • On the Blade, raw PHP and static HTML paths the focus is given by wayne-ui-theme.js. At startup it finds every .wsu-error-summary already on the page: the first takes focus, and the rest carry role="alert" so they are still announced without taking focus away from the one that already has it. A summary inserted afterward by Livewire, Turbo or htmx is observed as it arrives and gains role="alert" the moment it does, with focus left wherever it already was. Passing autofocus false opts an instance out of all of this, on every path and in every one of these cases. The Vue component coordinates the same claim on its own, new in 2.0.7: the first ErrorSummary displaying errors takes focus, and any instance that arrives while another is already showing errors renders role="alert" instead of stealing it, so a page mounting more than one form behaves the same whether it is Vue, Blade, raw PHP or static HTML. The claim is released when a summary's error list empties or the component unmounts, so the next surviving instance can take it.
  • The focus behavior on those three paths lives in the published copy of wayne-ui-theme.js, which arrived in 2.0.2. artisan vendor:publish does not overwrite a file that already exists, so an application that published its assets under 2.0.0 or 2.0.1 and then upgraded has the newer Blade view and the older script, and its summaries neither focus nor announce. Republish the wayne-ui-assets tag, or pass autofocus false, if you are upgrading from before 2.0.2.
  • For a radio group the target is the fieldset, not one of the radios inside it. The failure is "you did not answer", which belongs to the question rather than to any single option, so the fieldset carries the id the link points at and a tabindex="-1" of its own; without it, following the link scrolls the group into view and leaves focus on the body.

Accessibility

  • Focused on appearance, with tabindex="-1" (3.3.1).
  • Each entry is a real link to the control id, which is the fieldset itself for a radio group (2.4.4).
  • A heading, so it appears in the outline.

Success criteria in brackets refer to WCAG 2.2. Every one of them is checked by axe-core in both themes at three viewport widths before this page can be published. See Accessibility for what the gates are.

Props

Blade

AttributeDefaultWhat it does
errorsthe error bagPass an array to override.
bag'default'Named error bag.
titlegenerated from the error countThe heading.
id'error-summary'id of the summary element itself.
autofocustrueTakes focus on appearance. Set false to opt out and fall back to role="alert" instead.

Vue

PropType and defaultWhat it does
errorsObject, nullAny Laravel or Inertia error shape.
title / titleLevelString / Number, 2The heading.
idForFunction, identityMaps a field name to the id of its control.
autofocusBoolean, trueTakes focus on appearance. Falls back to role="alert" when off.