Live regions
How to tell a screen reader 'something just changed' — and which kind of nudge to use.
A live region is a part of the page whose content can change after the page has loaded, and which a screen reader should announce when it does. Form successes, error toasts, "saving..." indicators, chat messages — all candidates.
The naive approach — just dropping new text into the DOM — does nothing for screen readers. They've already moved on. You have to mark the region as live so the assistive technology watches it. There are two ways to do that, and three knobs to tune behavior. Let's walk through each.
Polite vs assertive
The base attribute is aria-live, with two useful values:
aria-live="polite"— wait until the user is idle, then announce. This is the default polite behavior; the announcement queues up rather than interrupting whatever the screen reader is reading right now. Use this for almost everything.aria-live="assertive"— interrupt whatever the screen reader is currently saying and announce immediately. Use this only for truly urgent messages: session timeout warnings, validation errors that block a critical action, real-time emergencies.
There's also aria-live="off", which is the same as having no aria-live attribute at all.
<!-- Polite: announced when the screen reader has a moment --> <div aria-live="polite" id="save-status"></div> <!-- Assertive: interrupts everything --> <div aria-live="assertive" id="session-warning"></div>
A common mistake is reaching for assertive because it sounds more reliable. It's not — it's more interrupting. Constant interruptions train screen-reader users to mute your tab. Default to polite and earn assertive for genuine emergencies.
Empty live regions must exist before the change. Adding the aria-live attribute and the new content in the same render is unreliable — many screen readers treat the region as new and skip the announcement. Render the region empty, then update its text.
role="status" and role="alert"
There are two role-based shortcuts that bake in common live-region settings:
role="status"is equivalent toaria-live="polite"plusaria-atomic="true". Use it for confirmation messages — "Saved," "Copied to clipboard."role="alert"is equivalent toaria-live="assertive"plusaria-atomic="true". Use it for urgent, critical messages.
<div role="status">Profile saved.</div> <div role="alert">Connection lost — your changes are not being synced.</div>
Both roles also imply landmark-ish semantics in some screen readers, which is generally a good thing — they're discoverable, not just announced once. Prefer them over hand-rolling aria-live unless you need to tune the other knobs below.
aria-atomic
When the content of a live region changes, the screen reader has a choice: read the whole region, or read only what changed. aria-atomic controls that.
aria-atomic="false"(default) — only the changed text is announced.aria-atomic="true"— the entire region is re-read every time, as one unit.
This matters most for compound messages like a clock or counter. Imagine a timer:
<!-- Without aria-atomic, only the changed digit might be read: "5" --> <div aria-live="polite"> Time remaining: <span id="seconds">12</span> seconds </div> <!-- With aria-atomic, the full sentence is announced each tick --> <div aria-live="polite" aria-atomic="true"> Time remaining: <span id="seconds">12</span> seconds </div>
The role="status" and role="alert" shortcuts already set aria-atomic="true" for you, which is almost always what you want for short messages.
aria-relevant
aria-relevant says which kinds of changes count. The values are space-separated tokens: additions, removals, text, or all. The default is additions text — added nodes and changed text. You can broaden it with removals (announce when something is deleted) or use all.
In practice, you almost never need to set aria-relevant. The default does the right thing for the typical "show a toast" or "append a chat message" pattern. Mention it on a code review when you see it being changed without a strong reason — it's one of the more bug-prone ARIA attributes.
Pitfalls
A short list of things that bite people:
- Empty region must exist first. Render the live region in the initial HTML, then update its text. Adding the region and the text together often produces silence.
- Don't pile up live regions. One per logical purpose. If you have ten different toasts trying to announce themselves at once, switch to a single shared toast region.
- Hidden ≠ off. A live region inside a
display: noneparent is not announced when text changes. If you want a region announced even when it's visually hidden, use the.visually-hiddentechnique (lesson 5), notdisplay: none. role="alert"is loud. It interrupts. Save it for genuine "you need to act now" messages.- Test with a real screen reader. Browser devtools won't show you whether your live region actually announces. NVDA on Windows or VoiceOver on macOS will.
A toast that fires every time a user clicks a button is rarely useful and often harmful — it interrupts whatever the screen reader was reading. Confirm only meaningful changes, not every interaction.
Try it yourself
aria-live="assertive"?role="status" and role="alert"?