WCAG 4.1.3 · Level AA · WCAG 2.1
Status Messages
Status messages can be programmatically determined through role or properties so they're announced by assistive tech without receiving focus.
- Principle
- Robust
- Guideline
- Compatible
- Level
- AA
- Added in
- WCAG 2.1
What it really means
Status messages — "Added to cart", "3 results found", "Saved", "You have unread messages" — must be programmatically determined through role or ARIA properties, so assistive tech announces them without the user having to move focus to find them.
The rule was introduced in WCAG 2.1 to close a long-standing gap: a sighted user sees a toast pop in from the corner, but an AT user typing in a search field hears nothing unless something takes focus — which would disrupt their typing.
Who it helps
- Screen-reader users, who hear updates without chasing focus.
- Braille users, whose display reflects the live region.
- Cognitive-disabled users, who benefit from consistent feedback timing.
How to test
- Turn on VoiceOver or NVDA. Trigger the status — add to cart, filter results, save a draft. Verify the screen reader speaks the message without you moving focus.
- Confirm the element carries an appropriate
roleoraria-livevalue, and was not empty at page load. - Don't
role="alert"for normal status — that's assertive and cuts off other announcements. Reservealertfor errors and warnings.
The three primary roles
| Role | Live-region level | Use for |
|---|---|---|
status | polite | General success ("Saved"), search counts, non-critical updates. |
log | polite | Chat messages, transcripts, append-only feeds. |
alert | assertive | Errors, session-expiring warnings, destructive-action confirmations. |
You can also use generic aria-live="polite" or aria-live="assertive"
on a region if the role doesn't fit semantically.
A failing pattern
// Visual-only toast; screen readers hear nothing.
<div className="toast">Added to cart</div>
// "3 results" update in a generic div — no live-region contract.
<div className="results-count">{count} results</div>A passing pattern
// Polite status — announced after current AT speech, no interrupt.
<div role="status" aria-live="polite">
{statusMessage}
</div>
// Search result counts — update the same region on every change.
function ResultsCount({ count }: { count: number }) {
return (
<p role="status" aria-live="polite" aria-atomic="true">
{count === 0
? "No results match your filters."
: `${count} result${count === 1 ? "" : "s"} found.`}
</p>
);
}
// Assertive — use sparingly for errors.
<div role="alert">Session expired. Please sign in again.</div>Key rules:
- The element with the role must exist in the DOM before it is
populated. Mounting an empty
<div role="status">on first render and updating its content later is reliable. Injecting a fresh element with a message already in it is flaky in some screen readers. aria-atomic="true"replaces the whole region on update — useful for "3 results" counters. Without it, AT may announce just the diff.- Don't overuse live regions. Every polite announcement queues up; three of them at once create a wall of speech.
Notes and edge cases
- Toasts that disappear quickly must stay long enough for AT to announce them. A 3-second toast often fails. Pair with an error summary for anything the user needs to act on.
- Form errors sometimes use
role="alert"on the error paragraph — that's fine for a single field but noisy for a whole form. Use an error summary instead. - Toasts with actions (Undo link) must not auto-dismiss before AT users can reach them. Provide a visible control and don't rely on hover-to-pause.
Related rules
- WCAG 3.3.1 Error Identification — the form-specific version.
- axe: aria-valid-attr-value.
- axe: aria-allowed-attr.