Categories
Why are my live regions not working?
Posted on by Patrick H. Lauke in Design and development
Live regions have a reputation for being "flaky" and inconsistent. While this can be attributed in part to shortcomings in current implementations, the problem can also be caused by developers misunderstanding how live regions are intended to work.
Setting the scene
Currently, the exact behaviour of live regions is arguably not very well explained in the ARIA specification, nor the Core-AAM specification. As a result, support can vary across different browser/screen reader combinations. While efforts are underway to make the specifications clearer, and to harmonise the behaviour across implementations, there are approaches that developers can use that stand a much better chance of working than others.
How live regions are supposed to work
In simple terms, when browsers encounter an element marked up as a live region (either explicitly with an aria-live="…"
attribute, or with a role
that has an implicit live region, such as role="status"
), they expose this information to screen readers in the accessibility tree. Whenever the content of the live region changes, browsers send out events through the platform's accessibility API. Screen readers need to register for these events, and then announce them to the user.
Live regions that are already "populated" don't work
One developer misstep that we often see with live regions is the use of containers that are already "populated" – live regions that already have content inside them that is meant to be announced by screen readers.
In some cases, developers include a live region directly in their page's markup, expecting it to be announced when the page is loaded.
<!-- Already contained in the HTML sent from the server -->
<div aria-live="assertive">
Announce this when the page loads
</div>
In other cases, a live region container is present in the page, but hidden – both visually, and from assistive technologies.
<!-- Pre-populated hidden live region container -->
<div id="notification" aria-live="assertive" class="hidden"></div>
.hidden { display: none; }
When developers then want a message to be announced by screen readers, they inject it into this hidden container, and then "unhide" the now populated live region container.
/* Get a reference to the hidden live region */
const liveRegion = document.getElementById('notification');
/* Generate text node for the message */
var message = document.createTextNode("Announce this when the container is unhidden");
/* Add message to the live region container */
liveRegion.appendChild(message);
/* Make the populated region appear (visually and to SRs) */
liveRegion.classList.remove('hidden');
By far the most common cases we see are live region containers – complete with actual content – being generated dynamically via JavaScript and inserted into the current page.
/* Generate live region container */
const newLiveRegion = document.createElement("div");
newLiveRegion.setAttribute("aria-live","assertive");
/* Generate text node for message */
var message = document.createTextNode("Announce this when the container is appended to the body");
/* Add message to live region container */
newLiveRegion.appendChild(message);
/* Add the populated live region to the page */
document.body.append(newLiveRegion);
All of these approaches generally fail to produce the desired result.
The ARIA specification is currently silent on whether or not the initial content inside a live region should be passed on to screen readers. The specification only refers to content changes in the section on Live Region Attributes, and updates in the definition for live region.
The Core-AAM specification – which more specifically deals with how browsers should interact with, and expose content to, the platform's accessibility APIs – doesn't provide any further insights on this aspect either – see the section on changes to document content or node visibility.
Because of this relative vagueness in the specifications, different browsers and screen readers currently can behave differently when it comes to live regions that are already "populated" with content, and whether or not that initial content is announced. However, following recent discussions within the ARIA Working Group (see Clarification of when live regions are meant to be announced by AT), it was confirmed that the intended behaviour is not to announce any initial content, and to only expose changes/updates – with one exception: containers with role="alert"
.
The exception: role="alert"
regions
For historical reasons, containers with role="alert"
receive special treatment. When a browser encounters a container with this role
, it generally fires an immediate alert event to the accessibility API. See the explanation for the alert
role in ARIA (emphasis added to the last sentence):
Alerts are assertive live regions, which means they cause immediate notification for assistive technology users. If the operating system allows, the user agent SHOULD fire a system alert event through the accessibility API when the WAI-ARIA alert is created.
As a result, even pre-populated containers with role="alert"
result in the initial content being announced by screen readers – but even here, there are inconsistencies between different browsers/screen readers.
In addition, one aspect to consider when using role="alert"
is that screen readers will announce them differently. For instance, NVDA and Jaws will first announce "Alert", followed by the actual content; in VoiceOver on macOS, alerts are preceded by a special sound/earcon. For this reason – and because role="alert"
will act as an assertive live region, which will interrupt the current output of a screen reader – you shouldn't use this role
unless it's actually an important alert.
Priming your live region for best results
Leaving aside role="alert"
, the most robust approach to using live regions is to make sure that the browser "sees" the empty live region container first.
Add your empty container directly in your page's markup:
<!-- Empty live region container present in the HTML -->
<div id="notification" aria-live="assertive"></div>
You can also go ahead and create the empty container dynamically using JavaScript:
/* Dynamically generate an *empty* live region container */
const newLiveRegion = document.createElement("div");
newLiveRegion.setAttribute("aria-live","assertive");
newLiveRegion.id = "notification";
document.body.append(newLiveRegion);
This gives screen readers time to register for any updates on that container being sent through the accessibility API.
Then, after a short delay (possibly using a setTimeout()
), you can inject content into the live region container.
/* Get a reference to the live region container */
const liveRegion = document.getElementById("notification");
/* Remove any existing child nodes */
while (liveRegion.firstChild) {
liveRegion.removeChild(liveRegion.firstChild);
}
/* Create new message and append it to the live region */
var message = document.createTextNode("Dynamic announcement to inject into the existing container");
liveRegion.appendChild(message);
Current live region behaviour
We recently tested how some of the more common approaches to live regions behave in current browser/screen reader combinations. While results are likely to change in future, the following table shows the current support at the time of writing.
Note that for the pre-populated tests, we only want to have role="alert"
working, as it is treated differently from other live regions. If other types of live regions are announced, that goes against expectations/the specification.
Populated live region on load | Populated live region via CSS | Populated live region via JS | Inject into existing live region | Generate live region via JS, then populate | |
---|---|---|---|---|---|
Chrome / NVDA | Only role="alert" inconsistently announced |
Only role="alert" (prefixed with "Alert") |
Only role="alert" (prefixed with "Alert") |
All announced (role="alert" not prefixed) |
All announced (role="alert" not prefixed) |
Edge / NVDA | Only role="alert" inconsistently announced |
Only role="alert" (prefixed with "Alert") |
Only role="alert" (prefixed with "Alert") |
All announced (role="alert" not prefixed) |
All announced (role="alert" not prefixed) |
Firefox / NVDA | Not supported | Only role="alert" (prefixed with "Alert") |
Only role="alert" (prefixed with "Alert") |
All announced (role="alert" prefixed with "Alert") |
All announced (role="alert" prefixed with "Alert") |
Chrome / JAWS 2024 | Only role="alert" inconsistently announced |
Only role="alert" (prefixed with "Alert") |
Only role="alert" (prefixed with "Alert") |
All announced (role="alert" not prefixed) |
All announced (role="alert" not prefixed) |
Edge / JAWS 2024 | Only role="alert" inconsistently announced |
Only role="alert" (prefixed with "Alert") |
Only role="alert" (prefixed with "Alert") |
All announced (role="alert" not prefixed) |
All announced (role="alert" not prefixed) |
Firefox / JAWS 2024 | Not supported | All announced (role="alert" prefixed with "Alert") |
All announced (role="alert" prefixed with "Alert") |
All announced (role="alert" prefixed with "Alert") |
All announced (role="alert" prefixed with "Alert") |
Chrome / Narrator | Only role="alert" inconsistently announced |
Only role="alert" (prefixed with "Alert") |
Only role="alert" (prefixed with "Alert") |
All announced (role="alert" not prefixed) |
All announced (role="alert" prefixed with "Alert") |
Edge / Narrator | Only aria-live="assertive" and role="alert" inconsistently announced |
All announced | All announced | All announced | All announced |
Firefox / Narrator | Not supported | Not supported | Not supported | Not supported | Not supported |
Chromium / Orca | Not supported | All announced | All announced | All announced | All announced |
Firefox / Orca | Not supported | Only role="alert" |
Only role="alert" |
role="alert" is not announcedall others work consistently |
role="alert" inconsistently announcedall others work consistently |
Safari / VoiceOver (macOS) | Not supported | Only role="alert" (prefixed with earcon) |
Only role="alert" (prefixed with earcon) |
All announced (aria-live="assertive" and role="alert" prefixed with earcon) |
All announced (aria-live="assertive" and role="alert" prefixed with earcon) |
Chrome / VoiceOver (macOS) | Not supported | Only role="alert" (prefixed with earcon) |
Only role="alert" (prefixed with earcon) |
All announced (aria-live="assertive" and role="alert" prefixed with earcon) |
All announced (aria-live="assertive" and role="alert" prefixed with earcon) |
Firefox / VoiceOver (macOS) | Not supported | Only role="alert" (prefixed with earcon) |
Only role="alert" (prefixed with earcon) |
Only aria-live="assertive" (prefixed with earcon), role="alert" (prefixed with earcon), and role="log" |
All announced (aria-live="assertive" and role="alert" prefixed with earcon) |
Safari / VoiceOver (iOS) | Only role="alert" inconsistently announced |
All announced | All announced | All announced | All announced |
Chrome / TalkBack | All announced | All announced | All announced | All announced | All announced |
Firefox / TalkBack | Not supported | Not supported | Not supported | Only aria-live="polite" , aria-live="assertive" , role="log" |
Only aria-live="polite" , aria-live="assertive" , role="log" |
Conclusion
Despite a few outliers and bugs, we can see that behaviour is relatively consistent.
In the case of pre-populated live regions, it's mostly only role="alert"
regions that announce the initial content as soon as the region is created.
For all other types of live regions, make sure to "prime" an empty live region container first, and only then make a change to the container's content to trigger an announcement.
Note that the above testing is far from exhaustive. Live regions have additional attributes – aria-atomic
and aria-relevant
– that define how announcements should be handled. Our testing did not include aria-live="off"
– which, despite its misleading name, is intended to trigger live region announcements, but only when the user is focused on the region – nor containers with role="marquee"
or role="timer"
, which have an implicit aria-live="off"
. Lastly, it's often a common "belt and braces" practice to combine a live region role
with an explicit aria-live
attribute, just to be on the safe side. All these possible permutations are likely to surface further browser/screen reader inconsistencies and outright bugs – see for instance Firefox #1885011 Live region with aria-atomic="true" not announced by screen reader.
Further reading
- Are we live?, Scott O'Hara
- output: HTML's native live region element, Scott O'Hara
- Accessible notifications with ARIA Live Regions (Part 1), Sara Soueidan
- ARIA Live Regions, Andrea de Souza
- Aria-live in JavaScript Frameworks, Mark Steadman
- Live Regions in React, Abbey Perini
- The aria-live attribute and the role alert, Orange
- The Many Lives of a Notification (video), Sarah Higley
Next steps
For more information about the ARIA, read our introduction to WAI-ARIA or find out more about how our assessments can help you identify issues in your websites, mobile applications, design systems, and other products and services.
We like to listen
Wherever you are in your accessibility journey, get in touch if you have a project or idea.