Live Regions
Accessible live region patterns for status messages, alerts, update timing, and implementation-neutral assistive-technology expectations
Overview
Live regions can expose dynamic content changes without moving focus. role="status" has polite live-region semantics; role="alert" has assertive semantics and should be reserved for urgent information. A reliable interoperability technique is to render an empty live-region container first and update its text later. Dynamically inserted alerts are not prohibited, but results vary by role, insertion sequence, browser, assistive technology, and timing.
WCAG Criteria:
- 4.1.3 Status Messages — qualifying status information must be programmatically determinable without receiving focus
Key requirements:
- Use
role="status"for non-urgent updates (cart counts, success messages) - Use
role="alert"for urgent errors and warnings - Prefer a rendered, empty live-region container and update its text for broadly compatible behavior
- Treat dynamic insertion behavior as interoperability evidence, not a universal WCAG rule
aria-atomic="true"causes the full region to be re-read on each change (useful for counters)
Status Update (Polite)
Cart Counter with role=status vs. No Live Region
View inaccessible code
<!-- No live region — screen reader never announces the cart update -->
<button onclick="
var el = document.getElementById('bad-cart');
var n = parseInt(el.textContent) + 1;
el.textContent = n + ' item' + (n === 1 ? '' : 's');
">
Add to Cart
</button>
<div id="bad-cart">Cart: 0 items</div>View accessible code
<!-- role="status" announces changes politely without interrupting -->
<button onclick="
var count = parseInt(document.getElementById('cart-count').dataset.count || 0) + 1;
document.getElementById('cart-count').dataset.count = count;
document.getElementById('cart-status').textContent =
'Cart: ' + count + ' item' + (count === 1 ? '' : 's');
">
Add to Cart
</button>
<!-- Live region exists on page load — content is updated, not the element -->
<div id="cart-status" role="status" aria-atomic="true">
Cart: 0 items
</div>Alert (Assertive)
One-Step Dynamic Alert vs. Rendered Empty Alert
Role and content are inserted together, which has inconsistent support across browser and AT combinations.
View inaccessible code
<!-- Role and content are inserted together; support varies by AT/browser. -->
<button onclick="
var err = document.createElement('div');
err.setAttribute('role', 'alert');
err.textContent = 'Session expired. Please log in again.';
document.body.appendChild(err);
">
Simulate Error
</button>View accessible code
<!-- Empty alert remains rendered; only its text changes. -->
<div id="error-banner" role="alert"></div>
<button onclick="
var banner = document.getElementById('error-banner');
banner.textContent = 'Session expired. Please log in again.';
">
Simulate Error
</button>
<!-- To clear: set textContent to ''. -->Key Concepts Reference
aria-live Values
| Value | Behavior | Implicit Role |
|---|---|---|
polite | Waits for user to finish current activity | role="status", role="log" |
assertive | Interrupts immediately | role="alert" |
off | No announcements (useful during auto-rotation) | — |
aria-atomic
aria-atomic="true"— the entire region is re-read on any change (good for counters: “Cart: 3 items”)aria-atomic="false"(default) — only the changed nodes are read (good for chat logs where only new messages matter)role="status"hasaria-atomic="true"by default;role="log"hasaria-atomic="false"by default
aria-relevant
Controls which types of changes trigger announcements:
additions— new nodes added (default for most live regions)removals— nodes removedtext— text content changedall— shorthand foradditions removals text
Most of the time, the default (additions text) is correct. Only set aria-relevant explicitly if you need removal announcements.
Throttling Rapid Updates
Rapid live region updates flood the speech queue, making the screen reader unusable. Best practices:
- Debounce updates — wait 500ms+ after the last change before updating the live region
- Announce at thresholds — “10 results”, “50 results”, “100 results” instead of every increment
- Use
aria-live="off"during auto-play/auto-scroll, switch to"polite"for manual interaction - Never use
role="alert"for frequent updates — it interrupts every time