Status Messages
Status messages can be programmatically determined through role or properties so they are announced by assistive technology without receiving focus.
Who this affects
Screen reader users, who otherwise never learn that a search returned 12 results, that an item was added to the basket, or that a form saved. The message appears on screen and is silently invisible to them.
The failure and the fix
Each example below is a self-contained page, loaded in a frame so its markup cannot affect this page. Open either one on its own to test it with a keyboard or screen reader.
Fails 4.1.3
Open the failing example in a new tab
fail.html — the problem
<h1>Search the catalogue</h1>
<form id="f">
<div class="field">
<label for="q">Search</label>
<input type="search" id="q" value="tide">
</div>
<button type="submit" class="button">Search</button>
</form>
<!-- FAILURE 1: the result count appears on screen and is never announced.
A screen reader user presses Search and hears silence. They have no idea
whether the search ran, found nothing, or is still loading. -->
<p id="count"></p>
<!-- FAILURE 2: the same for add-to-basket confirmation. -->
<div class="result">
<p>Tidal Flats, by A. Marsh</p>
<button type="button" class="button button--secondary" onclick="add()">Add to basket</button>
</div>
<p id="basket"></p>
<!-- FAILURE 3: a live region created at the same moment the message is put
into it. This is the subtle one, and it is everywhere: a live region must
already exist in the DOM, and be being observed, BEFORE the text changes.
Injecting <p role="status">Saved</p> announces nothing in most screen
readers. -->
<div id="late"></div>
<p><button type="button" class="button button--secondary" onclick="saveLate()">Save for later</button></p>
Meets 4.1.3
Open the passing example in a new tab
pass.html — the fix
<h1>Search the catalogue</h1>
<form id="f">
<div class="field">
<label for="q">Search</label>
<input type="search" id="q" value="tide">
</div>
<button type="submit" class="button">Search</button>
</form>
<!-- FIX 1: the live region is in the DOM from page load, EMPTY. The screen
reader starts observing it immediately, so when text is written into it
later the change is announced.
role="status" implies aria-live="polite" and aria-atomic="true": the
message waits for a natural pause instead of interrupting. -->
<p id="count" role="status"></p>
<div class="result">
<p>Tidal Flats, by A. Marsh</p>
<button type="button" class="button button--secondary" onclick="add()">Add to basket</button>
</div>
<!-- FIX 2: a second, separate live region. Two independent messages need two
regions, or one overwrites the other before it is read. -->
<p id="basket" role="status"></p>
<!-- FIX 3: the region exists up front here too. -->
<p id="late" role="status"></p>
<p><button type="button" class="button button--secondary" onclick="saveLate()">Save for later</button></p>
<!-- Choosing the right politeness:
role="status" / aria-live="polite" - waits for a pause. Use this for
almost everything: search results,
save confirmations, counts.
role="alert" / aria-live="assertive" - interrupts immediately. Reserve
it for errors and things the user
must act on now. Overusing it makes
a page hostile to listen to.
And the boundary of 4.1.3: it applies to messages that do NOT take focus.
If you move focus to the message - as an error summary does - then focus
does the announcing and a live region is unnecessary. -->
How to test it
Trigger each status update — search results, validation summaries, add-to-cart confirmations, loading states, auto-save notices. Confirm each is inside a live region that exists in the DOM before the message arrives, and confirm a screen reader actually announces it.
-
Automated
Scanners validate live region attributes when present. They cannot detect a status message that has no live region — the actual failure — and cannot verify announcement really happens.
This demo's failure is not machine-detectable. There is nothing for a rule to fire on. axe validates live-region attributes when they are present; the failure here is that no live region EXISTS, so the scanner sees an ordinary paragraph. A scanner also cannot press the button, so it never observes the update at all. 4.1.3 has to be tested by triggering each status change with a screen reader running.
Relevant axe rules:
aria-valid-attr-valuearia-allowed-attr
-
Keyboard
Unplug the mouse. Move through the page with Tab, Shift+Tab, Enter, Space and the arrow keys. See the keyboard testing script.
-
Screen reader
Listen to both examples with NVDA, JAWS, VoiceOver or TalkBack and compare what is announced. See the screen reader cheat sheets.
-
Visual
Zoom to 200% and 400%, narrow the viewport to 320 px, and apply the text-spacing overrides. See the visual testing procedures.
Related criteria
In the specification
- Understanding 4.1.3 Status Messages — the W3C explanation, intent and exceptions
- 4.1.3 in the WCAG 2.2 Recommendation — the normative wording
WCAG 2.2 Demo Suite