Accessibility pattern · Feedback and status

Error page

An error page is a page like any other: its title and its heading say what went wrong, in words, so the tab, the history and the first thing a screen reader reads all agree. Below that come the address that failed, a search, a short labelled set of links, and for server and connection errors a Try again button with a suggested wait that never reloads by itself.

WCAG criteria
7
Keyboard rules
4
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Error to show

Page title: Page not found · Kapda Co.

Error 404

We can't find that page

The link may be broken, or the page may have moved. Check the address, or search for what you need.

You asked for: kapda.co/fabrics/linen-kurtaa

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
Arrow Right or Arrow LeftIn Error to show, picks the next or previous kind of error; the page below changes to match.
TabMoves from the picker into the page: the search field and button (404) or Try again (500 and offline), then the helpful links.
EnterIn the search field, sends the search.
Enter or SpaceOn Try again, tries once more; the button says Trying again and stays focused, then a status message gives the result.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
A screen reader reads the 404 page from the topError 404. We can't find that page, heading level 3
Focus reaches the search fieldSearch the shop, search edit text
Tab reaches the linksHelpful links, navigation. Home page, link
Try again is pressedTrying again, button, unavailable
The attempt failsStill not working. Wait a moment, then try again.
The suggested wait runs outYou can try again now

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-errpage" data-ap-error-page>
  <fieldset class="ap-errpage__pick">
    <legend class="ap-errpage__legend">Error to show</legend>
    <div class="ap-errpage__track">
      <label class="ap-errpage__seg">
        <input class="ap-errpage__radio" type="radio" name="errpage-kind" value="404" checked />
        <span>Not found</span>
      </label>
      <label class="ap-errpage__seg">
        <input class="ap-errpage__radio" type="radio" name="errpage-kind" value="500" />
        <span>Server error</span>
      </label>
      <label class="ap-errpage__seg">
        <input class="ap-errpage__radio" type="radio" name="errpage-kind" value="offline" />
        <span>Offline</span>
      </label>
    </div>
  </fieldset>

  <div class="ap-errpage__window">
    <div class="ap-errpage__chrome">
      <p class="ap-errpage__tab">
        <span class="ap-errpage__favicon" aria-hidden="true"></span>
        <span class="ap-errpage__vh">Page title:</span>
        <span data-ap-title="404">Page not found · Kapda Co.</span>
        <span data-ap-title="500" hidden>Something went wrong · Kapda Co.</span>
        <span data-ap-title="offline" hidden>You're offline · Kapda Co.</span>
      </p>
    </div>

    <div class="ap-errpage__page">
      <section class="ap-errpage__variant" data-ap-variant="404" aria-labelledby="errpage-404-name">
        <svg class="ap-errpage__art" viewBox="0 0 96 64" aria-hidden="true" focusable="false"><rect class="ap-errpage__art-fill" x="8" y="10" width="62" height="44" rx="8"/><path class="ap-errpage__art-line" d="M8 22h62"/><circle class="ap-errpage__art-dot" cx="17" cy="16" r="1.6"/><circle class="ap-errpage__art-dot" cx="23" cy="16" r="1.6"/><path class="ap-errpage__art-soft" d="M20 33h24M20 42h14"/><circle class="ap-errpage__art-glass" cx="70" cy="40" r="12"/><path class="ap-errpage__art-line" d="m79 49 9 9"/></svg>
        <p class="ap-errpage__code">Error 404</p>
        <h3 class="ap-errpage__name" id="errpage-404-name" tabindex="-1">We can't find that page</h3>
        <p class="ap-errpage__what">The link may be broken, or the page may have moved. Check the address, or search for what you need.</p>
        <p class="ap-errpage__url"><span>You asked for:</span> <code translate="no">kapda.co/fabrics/linen-kurtaa</code></p>
        <form class="ap-errpage__search" role="search" aria-label="Shop" data-ap-search>
          <label class="ap-label" for="errpage-q">Search the shop</label>
          <div class="ap-errpage__search-row">
            <input class="ap-input" id="errpage-q" type="search" name="q" autocomplete="off" />
            <button type="submit" class="ap-btn">Search</button>
          </div>
        </form>
      </section>

      <section class="ap-errpage__variant" data-ap-variant="500" aria-labelledby="errpage-500-name" hidden>
        <svg class="ap-errpage__art" viewBox="0 0 96 64" aria-hidden="true" focusable="false"><rect class="ap-errpage__art-fill" x="20" y="8" width="56" height="20" rx="5"/><rect class="ap-errpage__art-fill" x="20" y="34" width="56" height="20" rx="5"/><rect class="ap-errpage__art-frame" x="20" y="8" width="56" height="20" rx="5"/><rect class="ap-errpage__art-frame" x="20" y="34" width="56" height="20" rx="5"/><circle class="ap-errpage__art-dot" cx="30" cy="18" r="2"/><circle class="ap-errpage__art-dot" cx="30" cy="44" r="2"/><path class="ap-errpage__art-soft" d="M42 18h22M42 44h22"/><path class="ap-errpage__art-line" d="m60 26-6 6h8l-6 6"/></svg>
        <p class="ap-errpage__code">Error 500</p>
        <h3 class="ap-errpage__name" id="errpage-500-name" tabindex="-1">Something went wrong on our side</h3>
        <p class="ap-errpage__what">It's not you: our server hit a problem loading this page, and we have been told. Your cart is safe.</p>
        <p class="ap-errpage__url"><span>Reference for support:</span> <code translate="no">7F3K-29QA</code></p>
        <div class="ap-errpage__retry">
          <button type="button" class="ap-btn ap-btn--primary" data-ap-retry>
            <svg class="ap-btn__icon ap-errpage__spin" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M20 12a8 8 0 1 1-2.3-5.7"/><path d="M20 4v4.5h-4.5"/></svg>
            <span data-ap-retry-label>Try again</span>
          </button>
          <p class="ap-errpage__wait" data-ap-wait>
            <span data-ap-wait-text>Suggested wait</span>
            <span class="ap-errpage__secs" role="timer" aria-live="off" translate="no" data-ap-secs>0:30</span>
          </p>
        </div>
      </section>

      <section class="ap-errpage__variant" data-ap-variant="offline" aria-labelledby="errpage-offline-name" hidden>
        <svg class="ap-errpage__art" viewBox="0 0 96 64" aria-hidden="true" focusable="false"><path class="ap-errpage__art-soft" d="M22 30a37 37 0 0 1 52 0M30 39a25 25 0 0 1 36 0"/><path class="ap-errpage__art-line" d="M39 48a13 13 0 0 1 18 0"/><circle class="ap-errpage__art-dot" cx="48" cy="55" r="2.6"/><path class="ap-errpage__art-line" d="M24 12 72 58"/></svg>
        <p class="ap-errpage__code">No connection</p>
        <h3 class="ap-errpage__name" id="errpage-offline-name" tabindex="-1">You're offline</h3>
        <p class="ap-errpage__what">Check your Wi-Fi or mobile data. Pages you opened before still work, and your cart is saved on this device.</p>
        <div class="ap-errpage__retry">
          <button type="button" class="ap-btn ap-btn--primary" data-ap-retry>
            <svg class="ap-btn__icon ap-errpage__spin" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M20 12a8 8 0 1 1-2.3-5.7"/><path d="M20 4v4.5h-4.5"/></svg>
            <span data-ap-retry-label>Try again</span>
          </button>
          <p class="ap-errpage__wait" data-ap-wait>
            <span data-ap-wait-text>Suggested wait</span>
            <span class="ap-errpage__secs" role="timer" aria-live="off" translate="no" data-ap-secs>0:30</span>
          </p>
        </div>
      </section>

      <nav class="ap-errpage__links" aria-labelledby="errpage-links-name">
        <h4 class="ap-errpage__links-name" id="errpage-links-name">Helpful links</h4>
        <ul>
          <li><a href="#errpage-home">Home page</a></li>
          <li><a href="#errpage-new">New arrivals</a></li>
          <li><a href="#errpage-orders">Track an order</a></li>
          <li><a href="#errpage-help">Help centre</a></li>
        </ul>
      </nav>
    </div>
  </div>

  <p class="ap-errpage__vh" role="status" data-ap-live></p>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.1.1 Non-text Content Level A

    The drawings are aria-hidden; the error code and heading say everything the picture does.

  • 1.3.1 Info and Relationships Level A

    The error is a section named by its heading, the links are a nav named by their own heading, and the search is a labelled search landmark.

  • 2.4.3 Focus Order Level A

    While Try again is busy it uses aria-disabled rather than disabled, so focus stays on it instead of falling back to the top of the page.

  • 2.4.2 Page Titled Level A

    Each kind of error has its own document title, shown above the page, so the tab and the history say what happened.

  • 2.4.6 Headings and Labels Level AA

    The heading says what went wrong in words ("We can't find that page"), with the code as a small label above it.

  • 3.2.5 Change on Request Level AAA

    The page only tries again when asked; a lost connection coming back is announced rather than acted on.

  • 4.1.3 Status Messages Level AA

    The result of Try again, the end of the suggested wait and a returning connection are polite status messages; focus stays where it is.

Usage

When to use it

Use it

  • Any request that cannot be served: a missing page (404), a server failure (500), a lost connection, a page someone may not see.
  • Single-page apps when a route fails: render this view, set the title, and move focus to its heading.

Use something else

  • A form field that failed: show the error beside the field and keep the person on the form.
  • An empty search or list: that is an empty state, not an error.

Common failures

How it usually goes wrong

  • Just a big 404

    A number means nothing to most people and is read as "four hundred and four". Say what happened in words, and show the code small, for support.

  • The title still says the old page

    In a single-page app, the tab keeps the last page's title, and a screen reader announces it. Set document.title for the error view.

  • Automatic reloads

    A page that refreshes itself every few seconds moves focus to the top and restarts the reading. Here nothing happens until Try again is pressed.

  • A disabled retry button

    Greying out Try again during a countdown leaves a dead control and gives no reason. The button stays usable; the wait is only a suggestion.

  • No way forward

    An error page with only a home link sends everyone back to the start. A search and a few labelled links get people where they meant to go.

  • Blaming the visitor

    "Invalid request" for a server fault is wrong and unkind. Say whose problem it is and whether anything was lost.

Notes

Building it

  • On a real page the heading is the h1 and the title is in the HTML; the demo uses h3 because it sits under this page's h2, and shows the title in a tab strip.
  • The option above also sets this browser tab's document.title while the demo is open; switch it on and watch the tab change with each kind of error.
  • Send the right status code (404, 500, 503) from the server too: it is what search engines and monitoring read, whatever the page says.
  • For the offline view, listen for the window's online event and announce it, but leave the reload to the person.
  • The search's form has role=search and its own name, and the links' nav is named by its heading, so neither repeats a landmark name the site already uses.

Sources: WCAG 2.2 Understanding 2.4.2: Page Titled · WCAG 2.2 Understanding 3.2.5: Change on Request

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction