Standards · WCAG 2.2

SC 4.1.3 Level AA WCAG 2.1

Status Messages

Status messages — confirmations, errors, progress updates, search results counts — must be announced to assistive technology without moving focus. Use role=status, role=alert, or aria-live on a region that already exists in the DOM.

What it asks

When the page tells the user something happened — “Item added to cart,” “3 results found,” “Connection lost,” “Saving…” — that message must reach screen-reader users without forcing focus to move. The information has to be programmatically determined as a status, so assistive technology can pick it up and announce it.

Three categories WCAG calls out:

  • Success / completion — “Profile saved,” “Email sent.”
  • Results of an action — “12 results,” “No matches found.”
  • State of the application — “Saving…”, “Reconnecting,” “Upload 40%.”

How to meet it

  • Put a live region in the DOM at page load — an empty element with role="status" (polite) or role="alert" (assertive). On update, write the new text into it.
  • For non-critical confirmations and results counts, use role="status" or aria-live="polite". Screen readers wait for the user to be idle, then announce.
  • For critical messages — form-submission errors, lost connection, session about to expire — use role="alert" or aria-live="assertive". The announcement interrupts.
  • Toast notifications need a live region. The toast container should exist in the DOM before the toast appears, with aria-live set, and the toast text injected as a child.
  • Pair aria-live with aria-atomic="true" if you want the whole region re-read on every update, not just the changed nodes.
  • For form errors, the inline error message can use role="alert", or move focus to a summary at the top of the form — but don’t do both; users only need to hear it once.

Common failures

  • Toast notifications rendered into an element that didn’t exist before the toast — the live-region listener never attaches, nothing is announced.
  • A success banner that gets inserted but lives outside any aria-live region — visible to sighted users, invisible to screen-reader users.
  • Search results count (“Showing 12 of 340”) that updates silently after filtering. Users tab back into the results unsure if anything changed.
  • Form validation that updates an inline <span class="error"> with no role="alert" or aria-live — error appears, screen reader stays silent.
  • Using aria-live="assertive" for every status update. Constant interruptions; screen-reader users disable the page.
  • Moving focus to a status message that’s not a heading or interactive — focus lands on a non-tabbable element, keyboard users lose their place.
  • “Saving…” / “Saved” indicators that flip a CSS class but never update any text content inside a live region.

Why it matters

Status messages are where modern single-page apps fall over. The page never reloads, focus rarely moves, and so the user’s only signal that something happened is a visual change — useless to anyone not looking at the screen. 4.1.3 forces parity: if a sighted user can see “Added to cart,” a screen-reader user can hear it. The fix is almost always cheap (one live region, populated on update) and the absence of it almost always breaks core flows — checkout, search, form submission, real-time chat.