Live-Region Announcements for Scroll-Revealed Content
Live regions are the accessibility tool most often reached for and most often misused on animated pages. The instinct — something changed visually, so announce it — produces a stream of narration about content that was already available, which is worse than saying nothing. This guide covers the decision of whether to announce at all, and how to do it correctly in the cases where the answer is yes. It sits under accessible transitions.
When to use this approach
The test is whether the available information changed, not whether anything moved:
- A reveal animation on existing content — no announcement. The element was in the accessibility tree before it faded in, and its text was reachable the whole time.
- A route change — a focus move, not a live region. Focusing the heading announces it, and adding a region on top says the page name twice.
- Content appended by scrolling — a polite announcement, once per batch. This is genuinely new information the reader did not ask for element by element.
- A background state change — saved, synced, failed. A polite region, or an assertive one when the change invalidates what the reader is doing.
Implementation
1. Render the region before you need it
<div id="status" role="status" aria-live="polite" aria-atomic="true" class="visually-hidden"></div>
role="status" implies aria-live="polite"; stating both is harmless and makes the intent explicit. aria-atomic="true" makes the region read as a whole rather than only reading the changed part, which is what you want for a short sentence.
2. Write into it, never replace it
const status = document.getElementById('status');
function announce(message) {
// clearing first makes repeated identical messages announce again
status.textContent = '';
requestAnimationFrame(() => { status.textContent = message; });
}
Replacing the region element itself removes the node the accessibility tree was observing, which is the same failure as inserting it late.
3. Batch announcements for appended content
function onBatchAppended(added, total) {
announce(`${added} more results, ${total} in total`);
}
One announcement per batch, after the batch has settled, carries strictly more information than one per item and does not make the page unusable.
4. Keep the region visually hidden, not hidden
.visually-hidden {
position: absolute;
inline-size: 1px;
block-size: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
display: none and visibility: hidden remove the region from the accessibility tree entirely, so nothing in it is ever announced. The visually-hidden pattern keeps it exposed while taking no visual space.
5. Say the same thing on the reduced-motion path
Whatever the animated path announces, the reduced-motion and unsupported paths must announce identically. An announcement is not motion, and a branch that skips it is skipping the wrong thing.
Verification
Turn on a screen reader and use the page normally for a minute. The test is not whether announcements happen but whether they are welcome — a page that narrates every reveal fails this immediately and obviously.
Then check each intentional announcement fires exactly once. Repeated identical messages are a common bug, caused by a region being written to on every render rather than on every change.
Confirm the region is present in the initial HTML rather than added by script. Viewing source, not the inspector, is the check — the inspector shows the post-script DOM in both the correct and incorrect versions.
Finally, verify with the page’s animations disabled. Announcements should be unchanged, which confirms they are driven by the data rather than by the effect.
Edge cases and gotchas
Announcing on every intersection callback. An observer used to drive reveals fires constantly during scrolling. Wiring an announcement to it produces continuous narration; the announcement belongs on the data change, not on the visibility change.
Regions inside a route that gets replaced. If the region lives in the swapped content, every navigation destroys and recreates it, and the first announcement after each navigation is lost. Keep status regions in the shell, outside the swapped region.
Identical consecutive messages. Most screen readers do not re-announce text that has not changed. Clearing the region before writing, on the next frame, is the standard workaround.
Over-using assertive. It interrupts whatever is currently being read. Reserve it for changes that invalidate the reader’s current action, and use polite for everything else.
Announcing a count that is still changing. During a fast scroll, several batches can arrive before the first announcement is read. Debouncing the announcement to the settled total avoids reading three stale counts in a row.
Browser-specific notes
Live regions are implemented by the browser and the screen reader together, and the variation between screen readers is larger than between engines.
VoiceOver on macOS and iOS is the most sensitive to regions being added late — an announcement from a region inserted in the same frame as its text is reliably missed. NVDA and JAWS are more forgiving of that specific mistake, which is why a bug of this shape often reaches production having been tested with one screen reader.
All three major screen readers respect aria-atomic and read the whole region, and all three suppress a repeated identical string. The clear-then-write technique works across all of them.
Firefox and Chromium expose the same accessibility tree semantics here; Safari’s tree is equivalent for these purposes. There is no engine-specific handling to write.
Frequently Asked Questions
Should a scroll-driven reveal ever be announced?
Only if the content was genuinely not available before — which for a reveal built from opacity and transform it always was. If a reveal is hiding content from the accessibility tree, that is a bug to fix in the reveal rather than a reason to add an announcement.
How verbose should a batch announcement be?
One short sentence carrying the delta and the total. “Twelve more results, forty-eight in total” tells a reader everything they need in about two seconds. Longer messages are read in full every time and quickly become something the reader wants to escape from.
Can I use a live region instead of moving focus on navigation?
You can, and it is worse. A live region announces the change but leaves the keyboard user’s position at the top of the document, so the next Tab does not continue from the new content. Focus does both jobs; the region does one.
Does aria-live work on elements that animate?
Yes — the region’s own styling and animation are irrelevant to whether its content changes are observed. What matters is that it remains in the accessibility tree, which rules out animating it with display or visibility but permits opacity and transform like anything else.
Is role="status" different from aria-live="polite"?
They produce the same live-region behaviour, and role="status" additionally gives the element a landmark-ish role that some screen readers expose in their element lists. In practice using the role and letting the implicit politeness apply is the tidier choice, and adding the explicit attribute alongside it costs nothing and documents the intent for the next reader of the markup.
Where they genuinely differ is in what a screen reader may do with the element outside of an announcement — a status region can be navigated to and re-read, which is occasionally useful for a message the reader wants to hear again.
What about aria-busy during a batch update?
Setting aria-busy="true" on the container while a batch is being inserted, and clearing it afterwards, tells assistive technology to hold off describing a partially-updated region. It is worth doing for large updates and unnecessary for small ones. Combined with a single announcement after the batch settles, it gives a clean sequence: the region goes quiet, the content changes, and one sentence describes what happened.
Do live regions work when the tab is in the background?
Announcements from a background tab are generally suppressed, which is correct — a screen-reader user working in another tab should not hear updates from this one. It does mean an announcement queued while the tab was hidden may never be read, so anything the reader must know needs to be visible in the page as well as announced.
Related
- Managing focus after a view transition — the tool that usually replaces a live region
- Screen-reader testing for animated pages — how to check announcements are welcome rather than merely present
- WCAG 2.2 Animation Compliance — the criteria these obligations sit under
- Accessible Transitions: Focus & Announcements — the four obligations in priority order