Overview
Live regions allow screen readers to announce dynamic content changes without the user having to move focus. Two levels exist: role="status" (polite — waits for the user to finish what they’re doing) and role="alert" (assertive — interrupts immediately). The most critical rule: the live region element must exist in the DOM before content is injected into it.
WCAG Criteria:
- 4.1.3 Status Messages — status updates must be announced without moving focus
Key requirements:
- Use
role="status"for non-urgent updates (cart counts, success messages) - Use
role="alert"for urgent errors and warnings - The live region element must be present in the DOM on page load
- Do not use
aria-liveon dynamically created elements — it won’t work 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
Inaccessible
<!-- 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>Live Preview
Cart: 0 items
Accessible
<!-- 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>Live Preview
Cart: 0 items
Alert (Assertive)
Persistent Alert Region vs. Dynamically Created Error
Inaccessible
<!-- Dynamically created element — live region property not recognized -->
<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>Live Preview
The dynamically created alert element will not be announced by most screen readers.
Accessible
<!-- Alert container exists in DOM from page load — only content changes -->
<div id="error-banner" role="alert" hidden></div>
<button onclick="
var banner = document.getElementById('error-banner');
banner.textContent = 'Session expired. Please log in again.';
banner.removeAttribute('hidden');
">
Simulate Error
</button>
<!-- To clear: set textContent to '' and re-hide -->Live Preview