Announcing Streamed Content with Live Regions
Content that streams in after first paint is visible to a sighted visitor and silent to everyone else unless something announces it. The mechanism is a live region, and the two mistakes — creating it too late and putting the content inside it — account for most implementations that do nothing. This is the announcement half of accessibility of deferred hydration.
Prerequisites
Implementation Steps
Step 1 — Ship the empty region with the document
<!-- Server-rendered, present from first paint, empty on purpose. -->
<div id="reviews-status" role="status" aria-live="polite" class="visually-hidden"></div>
<section id="reviews" aria-busy="true">
<!-- Placeholder, hidden from assistive technology because the status
region above is what carries the information. -->
<div class="skeleton" aria-hidden="true"></div>
</section>
Step 2 — Write a summary when the content lands
function onReviewsArrived(count) {
const section = document.getElementById('reviews');
section.setAttribute('aria-busy', 'false');
// Announce the ARRIVAL, not the content. The reviews themselves are in the
// section, navigable at the visitor's own pace.
const status = document.getElementById('reviews-status');
status.textContent = count === 0
? 'No reviews yet.'
: `${count} reviews loaded.`;
// Clear it after a moment so the message is not re-read when focus
// later moves through the region.
setTimeout(() => { status.textContent = ''; }, 4000);
}
Step 3 — Keep the placeholder out of the accessibility tree
A skeleton is a visual affordance. Marked aria-hidden, it stops being announced as a run of empty groups, and the status region carries the information instead. The aria-busy attribute on the section tells assistive technology that the region is still filling, which some screen readers use to avoid announcing partial content.
Step 4 — Reserve assertive for genuine urgency
An assertive region interrupts whatever is being spoken. That is correct for “your session expires in one minute” and wrong for “twelve reviews loaded”. If a page has more than one or two assertive regions, they are almost certainly miscategorised.
Verification
Test with a real screen reader; automated tooling can confirm the region exists with the right attributes but cannot tell you whether the announcement was made, which is the only thing that matters. Load the page with the connection throttled, wait without interacting, and listen. One announcement, at the right moment, in the right tone.
Then test the awkward cases. Trigger two regions arriving within a second of each other and confirm the announcements queue rather than clobbering one another — a polite region that is written twice in quick succession may announce only the second value. And navigate through the page after the announcement to confirm the status region does not re-read its message, which is why the timeout clears it.
What to Announce, and What to Leave Alone
A useful rule: announce state changes the visitor did not cause, and stay silent about ones they did.
Content finishing loading is a change they did not cause, so it deserves a short announcement. A filter they applied producing new results is a change they did cause — the interaction itself provided the feedback, and announcing it repeats what they already know. The exception is when the caused change is far from the control: applying a filter in a sidebar that updates a count in the header warrants an announcement, because nothing about the interaction told them the header moved.
Two anti-patterns are worth naming. Announcing progress — loading, still loading, almost there — turns a wait into a stream of interruptions; one message when the wait begins and one when it ends is enough. And announcing structure rather than substance, such as “region updated”, tells the visitor that something happened without telling them what, which forces them to go and look. A summary with a number in it is almost always the right length: it is informative enough to act on and short enough to ignore.
There is a final category worth planning for: content that arrives while the visitor is somewhere else entirely. A fragment that lands after the visitor has scrolled two screens down, or has moved focus into an unrelated form, should still be announced politely — but the announcement is only useful if it says where the change happened. “Twelve reviews loaded below the description” costs four extra words and saves a visitor from hunting through the page for something they were told exists.
The same reasoning applies to failures. “Reviews could not be loaded” is better than “an error occurred”, and “Reviews could not be loaded — the section has a retry button” is better still, because it tells the visitor both what is missing and what they can do about it. Live regions are one of the few places where writing slightly longer text improves the experience rather than degrading it, because the visitor cannot see the context that a sighted reader gets for free.
Troubleshooting
Why does my live region announce nothing?
Almost always because it was created and populated in the same task. Assistive technology watches an existing region for changes; a region that appears already containing text looks like ordinary new content rather than an update. Ship the empty region in the server-rendered HTML and write into it later, and the announcement becomes reliable across screen readers.
Should the streamed content itself go inside the live region?
No. Putting a large block of content in a live region causes the whole thing to be read aloud, interrupting whatever the visitor was doing and taking as long as the content is long. Announce a short summary — twelve reviews loaded — in the region, and place the content in its normal position in the document where it can be navigated at whatever pace suits them.
When is assertive the right choice?
When the update changes what the visitor should do next: a submission failed, a session is about to expire, an item they were reading has been removed. Everything else, including all routine content arrival, is polite. Assertive interrupts speech mid-sentence, and a page that does that for loading notifications is exhausting to use.
Related
- Keeping Focus Stable During Island Activation — the other half of an invisible activation.
- Accessibility of Deferred Hydration — the checklist and the failure table.
- Fallback UI & Skeleton Strategies — the placeholder this hides from the accessibility tree.
← Back to Accessibility of Deferred Hydration