Accessibility pattern · Feedback and status

Status message

A status message is news people should hear without leaving what they are doing: an item added, a number of results, a draft saved. Each one is written into a role=status element that was in the page from the start, once per change, and emptied first when the same words need saying again.

WCAG criteria
5
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.

New arrivals

Cart 2 items

6 products found

  • Linen shirt ₹1,899
  • Linen trousers ₹2,299
  • Khadi kurta ₹2,450
  • Cotton dupatta ₹950
  • Silk stole ₹1,650
  • Linen tote bag ₹799

Gift message

No changes yet

Saved as you type. Up to 200 characters.

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
Enter or SpaceOn Add to cart, adds the item. Focus stays on the button; the cart count changes and the result is announced once.
TypeIn Filter products, filters the list when typing pauses; the number found is announced then, not on every key.
TypeIn the gift message, saves a second and a half after typing stops, then announces the time it was saved.
Enter or SpaceOn Save now, saves at once; the time is announced again even when it has not changed.

Screen readers

What it announces

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

WhenExpected announcement
Add to cart is pressed for the linen shirtLinen shirt added. 3 items in cart
The cart count beside it changesNothing: it is not a live region, so the news is heard once
Typing linen in the filter, then pausing3 products found
Typing pauses in the gift messageSaved at 10:42 (Saving… is shown, not announced)
Save now is pressed in the same minuteSaved at 10:42, again, because the region was emptied first

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-status-message" data-ap-status-message>
  <section class="ap-status-message__card" aria-labelledby="status-shop-h">
    <div class="ap-status-message__top">
      <h3 class="ap-status-message__h" id="status-shop-h">New arrivals</h3>
      <a class="ap-status-message__cart" href="#status-cart">
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 7h12l-1 13H7Z"/><path d="M9 7a3 3 0 0 1 6 0"/></svg>
        <span>Cart</span>
        <span class="ap-status-message__badge"><span data-ap-count>2</span><span class="ap-status-message__sr"> items</span></span>
      </a>
    </div>

    <div class="ap-field">
      <label class="ap-label" for="status-filter">Filter products</label>
      <input class="ap-input" id="status-filter" type="search" autocomplete="off" spellcheck="false" data-ap-filter />
    </div>
    <p class="ap-status-message__found" role="status" data-ap-found><span data-ap-n>6</span> <span>products found</span></p>

    <ul class="ap-status-message__list" aria-labelledby="status-shop-h">
      <li class="ap-status-message__item">
        <span class="ap-status-message__what">
          <span class="ap-status-message__name" id="status-name-1">Linen shirt</span>
          <span class="ap-status-message__price">₹1,899</span>
        </span>
        <button type="button" class="ap-btn ap-status-message__add" id="status-add-1" aria-labelledby="status-add-1 status-name-1" data-ap-add>
          <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 5v14M5 12h14"/></svg>
          Add to cart
        </button>
      </li>
      <li class="ap-status-message__item">
        <span class="ap-status-message__what">
          <span class="ap-status-message__name" id="status-name-2">Linen trousers</span>
          <span class="ap-status-message__price">₹2,299</span>
        </span>
        <button type="button" class="ap-btn ap-status-message__add" id="status-add-2" aria-labelledby="status-add-2 status-name-2" data-ap-add>
          <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 5v14M5 12h14"/></svg>
          Add to cart
        </button>
      </li>
      <li class="ap-status-message__item">
        <span class="ap-status-message__what">
          <span class="ap-status-message__name" id="status-name-3">Khadi kurta</span>
          <span class="ap-status-message__price">₹2,450</span>
        </span>
        <button type="button" class="ap-btn ap-status-message__add" id="status-add-3" aria-labelledby="status-add-3 status-name-3" data-ap-add>
          <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 5v14M5 12h14"/></svg>
          Add to cart
        </button>
      </li>
      <li class="ap-status-message__item">
        <span class="ap-status-message__what">
          <span class="ap-status-message__name" id="status-name-4">Cotton dupatta</span>
          <span class="ap-status-message__price">₹950</span>
        </span>
        <button type="button" class="ap-btn ap-status-message__add" id="status-add-4" aria-labelledby="status-add-4 status-name-4" data-ap-add>
          <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 5v14M5 12h14"/></svg>
          Add to cart
        </button>
      </li>
      <li class="ap-status-message__item">
        <span class="ap-status-message__what">
          <span class="ap-status-message__name" id="status-name-5">Silk stole</span>
          <span class="ap-status-message__price">₹1,650</span>
        </span>
        <button type="button" class="ap-btn ap-status-message__add" id="status-add-5" aria-labelledby="status-add-5 status-name-5" data-ap-add>
          <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 5v14M5 12h14"/></svg>
          Add to cart
        </button>
      </li>
      <li class="ap-status-message__item">
        <span class="ap-status-message__what">
          <span class="ap-status-message__name" id="status-name-6">Linen tote bag</span>
          <span class="ap-status-message__price">₹799</span>
        </span>
        <button type="button" class="ap-btn ap-status-message__add" id="status-add-6" aria-labelledby="status-add-6 status-name-6" data-ap-add>
          <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 5v14M5 12h14"/></svg>
          Add to cart
        </button>
      </li>
    </ul>
    <p class="ap-status-message__added" role="status" data-ap-added></p>
  </section>

  <section class="ap-status-message__card" aria-labelledby="status-gift-h">
    <div class="ap-status-message__top">
      <h3 class="ap-status-message__h" id="status-gift-h">Gift message</h3>
      <p class="ap-status-message__save" role="status" data-ap-save>
        <span class="ap-status-message__busy" aria-hidden="true" hidden data-ap-busy>
          <span class="ap-status-message__dots"><span></span><span></span><span></span></span>
          Saving…
        </span>
        <span class="ap-status-message__done" data-ap-done>No changes yet</span>
      </p>
    </div>
    <div class="ap-field">
      <label class="ap-label" for="status-note">Message on the card</label>
      <textarea class="ap-input" id="status-note" rows="3" maxlength="200" aria-describedby="status-note-hint" data-ap-note></textarea>
      <p class="ap-hint" id="status-note-hint">Saved as you type. Up to 200 characters.</p>
    </div>
    <div class="ap-status-message__actions">
      <button type="button" class="ap-btn" data-ap-save-now>Save now</button>
    </div>
  </section>
</div>

WCAG 2.2

What it meets

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

  • 1.3.1 Info and Relationships Level A

    Each message sits in the section it belongs to, beside the controls that cause it, and the filter and the gift message have real labels.

  • 2.5.3 Label in Name Level A

    Each Add to cart button's name starts with its visible words, then adds the product, so a voice command for what is on screen works.

  • 3.2.2 On Input Level A

    Typing in the filter changes the list and the count, but never moves focus or submits anything.

  • 4.1.2 Name, Role, Value Level A

    The Add to cart buttons are named with their products, such as Add to cart Linen shirt, so the list of buttons makes sense on its own.

  • 4.1.3 Status Messages Level AA

    The cart, the result count and the save time are told through role=status elements, heard without focus moving to them.

Usage

When to use it

Use it

  • The result of an action that leaves focus where it is: an item added, a filter applied, a draft saved.
  • Progress or a count that changes while people work, said once it settles.

Use something else

  • Errors or risks that need attention now: use role=alert in an alert banner.
  • Messages that need an answer: use a dialog.
  • A change that takes people somewhere new, like a new page or step: move focus to its heading instead.

Common failures

How it usually goes wrong

  • Moving focus to say something

    Focusing a message to make it heard pulls people away from the button or field they were using. A status message is heard where they are.

  • A count announced on every key

    Updating a live count on each keystroke queues a string of numbers over the typing. The filter here waits for a pause and says one number.

  • The same news twice

    With both the cart badge and the message live, the count is said twice. Only the message is a live region; the badge changes quietly.

  • A repeat that goes unheard

    Writing the same words into a region again may not count as a change. Emptying it first and writing a moment later makes it news again.

  • A region added with its message

    A role=status element inserted with its text already inside is often missed. These regions are in the HTML from the start, empty or holding the last message.

  • Saving and saved, every time

    Announcing Saving… and then Saved for each autosave doubles the chatter while someone types. Saving… is shown but kept out of the region.

Notes

Building it

  • role=status is polite and atomic: it waits for the screen reader to finish, then reads the whole region. Use it for results, confirmations and progress; role=alert interrupts and is for problems.
  • The region must exist, and be rendered, before its text changes, because screen readers announce changes to regions they already know about. One hidden with display: none or the hidden attribute is not in the accessibility tree, so showing it and filling it at the same moment is often missed.
  • Write numbers into their own element inside a fixed phrase, as the counts here do, so the words can be translated and the number never becomes part of them.
  • Saving… sits in the region with aria-hidden="true", so it is shown but not announced, and each save is heard once, as the time it finished.
  • The cart link uses a # address in the demo, where it stands for the cart page.

Sources: Understanding SC 4.1.3: Status Messages · Technique ARIA22: Using role=status to present status messages · WAI-ARIA 1.2: the status role

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