4.1.3 Status Messages

WCAG 2.2 · 4.1.3 AA Robust

What it requires

A status message is content that tells the user about the outcome of an action, the progress of a process, the existence of an error, or a change of state — without that content receiving keyboard focus. Examples include “3 results found”, “Item added to cart”, “Saving…”, and inline form-validation summaries that appear after submit.

Criterion 4.1.3 requires that such messages can be programmatically determined through role or properties, so assistive technology can announce them to the user without moving focus to them. In practice this means exposing the message through an appropriate ARIA live region (for example role="status", role="alert", or an aria-live region) so a screen reader speaks it the moment it appears.

  • Blind and low-vision screen-reader users — if a status is only conveyed visually and focus does not move to it, they never learn the action succeeded, failed, or produced new results.
  • Users with cognitive or attention disabilities who benefit from a clear, timely confirmation rather than having to hunt the page for what changed.
  • Magnifier users, who may have a small status update appear far outside their current viewport and miss it entirely without an announcement.

How to detect it

Detecting Status Messages failures
Check How to do it
Manual inspection Trigger each dynamic update (search, add to cart, save, validation). Confirm the new text sits in a container with role="status", role="alert", or aria-live, and that focus does not move to it.
Screen reader With NVDA, JAWS, or VoiceOver running, perform the action without touching the keyboard. The message should be announced automatically. Errors should interrupt (assertive); informational updates should be polite.
Keyboard Verify the update does not steal focus or disrupt the user’s place — 4.1.3 is for messages that appear without a focus change.
Zoom / magnification At 200–400% zoom, confirm important confirmations are still perceivable or announced even when they render off-screen.
Automated tools Tools such as axe can flag some empty or misused live regions, but cannot reliably detect a missing status message — whether content qualifies as a status is contextual. Manual and screen-reader testing is required.

How to fix it

  1. Identify every update that conveys status without moving focus: results counts, confirmations, progress, busy states, and validation summaries.
  2. Wrap each in a live region — role="status" (polite) for routine updates, role="alert" (assertive) for errors and time-sensitive messages.
  3. Render the live-region container in the DOM before the message text, then inject the text into it, so assistive technology is already observing it when it changes.
  4. Keep messages concise and don’t move focus to them. Reserve alert for genuinely interruptive content to avoid overwhelming users.
  5. Re-test with a screen reader to confirm each message is announced exactly once.
<!-- Present and empty on load; AT observes it before text arrives -->
<div role="status" aria-live="polite" class="search-status"></div>

<script>
  // After the search completes, inject the message:
  document.querySelector('.search-status').textContent = '3 results found.';
</script>

Copy-paste tests

Automated coverage

There is no fully automated axe-core rule that confirms 4.1.3 Status Messages: a tool cannot know which DOM changes are meant to be announced. This criterion needs manual review using the console check and steps below.

Run this in the browser console

status-messages-audit.js
// READ-ONLY: lists candidate live regions and roles. Modifies nothing.
const live = [...document.querySelectorAll(
  '[aria-live], [role=status], [role=alert], [role=log], [role=progressbar], output'
)];
console.table(live.map(el => ({
  tag: el.tagName.toLowerCase(),
  role: el.getAttribute('role') || '',
  ariaLive: el.getAttribute('aria-live') || '',
  text: (el.textContent || '').trim().slice(0, 60)
})));
live.forEach(el => { el.style.outline = '2px solid magenta'; });
console.log(live.length + ' live region(s) found.');

What to check manually: trigger a real status change (e.g. submit a form, run a filter) and confirm a screen reader announces it without moving focus; and verify success/error feedback that should be announced actually sits inside one of the highlighted regions.