Modern web applications are filled with status updates: toasts, notifications, a loading screen, and in general, any relevant change you are noticing on screen. A sighted user will be able to notice these status updates, but what about screen reader users? You need to ensure that they receive these important updates as well.
This is where ARIA-live regions come into play. It’s a combination of markup and ARIA roles and properties; when content is injected into it with JavaScript, it will announce any change in its content to assistive technology devices. Having a solid understanding ARIA-live regions is helpful for making apps and websites that are accessible for everyone.
In this article, I’ll discuss available options you can use to create ARIA-live regions, each with different purposes. I’ll also demonstrate some tools you can use to create ARIA-live regions in modern JavaScript frameworks like React, Angular, and Vue.js.
Jump ahead:
There are multiple ways to make an ARIA-live region, including some ARIA roles with ready-made markup. However, you can also use a <div>
along with specific ARIA-live region attributes to create a custom live region. These attributes can have quite inconsistent results between different assistive technologies, so keep that in mind before attempting to create a custom live region markup by yourself. A little later in the article, we’ll review the ARIA roles.
aria-live
The aria-live
attribute defines at what moment assistive technologies will announce a change of content to the live region. This attribute has three values: off
, polite
, and assertive
. off
means the content will not be announced, so let’s focus on the other two attributes:
The polite
attribute will set up the region to wait until the screen reader announces the content is reading to announce whatever was injected in the live region.
On the other hand, an assertive
live region will announce the content immediately, interrupting any other action a screen reader is doing at the moment. For this reason, assertive
live regions should be used when you need to provide critical or time-sensitive information to assistive technology users.
aria-atomic
The aria-atomic
attribute will tell screen readers if the live region’s content will be announced in its entirety or only the parts that receive a change.
If you set this attribute to false
, the live region will only announce the content that has changed. If you set it to true
, it will announce all the content at once. To further clarify, let’s suppose we have this live region:
<div aria-live="polite"> <p>The amount spent is of <span>$25.00</span></p> </div>
If we add the aria-atomic
attribute and then change the content inside the span
to $30.00, this is what should happen:
<div aria-live="polite" aria-atomic="false"> <p>The amount spent is of <span>$30.00</span></p> </div>
If the attribute’s value is false
, like above, it will just announce the content in the span
. In this case, it will say “$30.00.” If the attribute’s value is true
, it will announce the entire string. In this case, it will say “The amount spent is $30.00.”
At least this is how it should work in theory. According to aria-atomic tests made by a11ysupport, some screen readers (mainly JAWS) have issues reading an inner content’s change when the attribute is set to false
. So if you’re going to use this attribute, keep this in mind.
aria-relevant
The aria-relevant
attribute allows you to specify to assistive technologies the type of updates you want the live region to announce.
This attribute has multiple values and you can add several of them separated by a space:
Value | Description |
---|---|
additions |
Announces when a new node is added inside the live region |
text |
Announces when any text or text alternative (like aria-label or alt for img elements) is added inside the live region |
removals |
Announces when any text or text alternative is removed from the live region |
additions text (default) |
Announces text changes and additions of nodes inside the live region |
all |
Announces any kind of change inside the live region (the equivalent of writing additions text removals ) |
The support for this attribute is quite inconsistent. As you can see in a11ysupport’s entry about aria-relevant
, it only has partial support. Mainly, it fails to make a differentiation in behavior between additions
and additions text
. Even more importantly, it directly fails to behave as needed with the removals
attribute with most screen readers. So, keep this in mind if you plan to use this attribute.
Now that we’ve reviewed how to create a custom ARIA-live region, let’s take a look at using ARIA roles
with prebuilt markup.
role="status"
According to W3C specs, role="status"
is created to “advisory information for the user but is not important enough to justify an alert
” In other words, this attribute is created to give users information about any relevant change that does not require immediate attention or action.
role="status"
is great for things like message notifications, success messages, or any type of event that is not urgent and can be attended to at any time. This region has the implicit attributes of aria-live="polite"
and aria-atomic="true"
. However, according to a11ysupport’s tests on the status
role, Orca (Linux’s screen reader) and NVDA won’t take into consideration the aria-atomic
implicit value for role="status"
. Therefore, I suggest that when you use this live region, you add the aria-atomic="true"
attribute in an explicit form, like so:
<div role="status" aria-atomic="true"> Your message was successfully sent! </div>
Other than that, role="status"
is a pretty well-supported ARIA role and will likely be one of your main tools for creating live regions.
role="alert"
role="alert"
is created to provide the user with information that is both important and time-sensitive. Alerts that inform the user that their session is about to end, that information wasn’t saved, or any other important error message are good use cases for role="alert"
.
This region has the implicit values of aria-atomic="true"
and aria-live="assertive"
. Due to how “intrusive” it feels – it will stop any action the screen reader is doing – remember to use it sparingly and only when needed. Here’s how the markup for this type of live region should look:
<div role="alert"> Your changes were not saved due to a network error. Please try again </div>
If you need to add interactive elements to a status message with this role, use role="alertdialog"
instead of role="alert"
. For example, it may be expected that the user will close a status message with a <button>
.
role="log"
According to W3C specs, the role="log"
role creates a live region “where new information is added in a meaningful order and old information may disappear.” This is useful in cases where old information is stored inside a live region and there is a certain order that needs to be respected, like messaging history or a change log.
This live region has the implicit values aria-live="polite"
and aria-atomic="false"
, meaning all changes are announced without interrupting any action. Also, it will just announce the part of the markup that is being changed.
If you change the aria-atomic
value to true
, it will announce all the content each time new content is injected, which is not desirable. Here’s how the markup for this type of live region might look:
<div role="log"> <ul aria-label="Message story"> <li> <span>Gabrielle: </span> Hi, I'm Gabrielle from XYZ Co. How may I help you? </li> <li> <span>Diana: </span> Hi, I'm Diana. I need to cancel my appointment. </li> </ul> </div>
The role="log"
live region has decent support, except for Narrator with Microsoft Edge, as you can see in a11ysupport’s test on role="log"
. If Narrator and Microsoft Edge are scenarios that need to be considered, you might be better off using a custom region with aria-live="polite"
and aria-atomic="false"
. The log
role is not needed as often as the status
and alert
roles, but it’s still important to keep it in mind for certain use cases.
There are a couple of extra live region roles, marquee
and timer
, but due to their poor support, using them is not recommended. By default, both have the attribute aria-live="off"
which means they won’t announce any change of content inside the live regions.
According to W3C specs, role="timer"
was created to contain “a numerical counter which indicates an amount of elapsed time from a start point, or the time remaining until an endpoint.” So, this role would be ideal for either a timer function (to indicate time elapsed) or a stopwatch function (to indicate time remaining).
role="marquee"
was created to add nonessential information that is continually updated. Things like stock price changes or charts would be ideal for this type of live region.
Since these roles are unsupported, it may be helpful to use a workaround. For example, to provide an alert about the remaining time left to complete a task, you could use role="status
or role="alert"
instead.
<output>
According to HTML specs, the <output>
HTML element is designed to expose “the result of a calculation performed by the application, or the result of a user action.”
This element is not an ARIA-live role. It should be used only inside or outside forms to show screen reader users the result of a calculation or action within the form. This element has the following attributes:
for
: This is a list of the related form element IDs, separated by spacesform
: This attribute helps to associate the output
element with any form; it’s only necessary if you use it outside a form elementname
: This represents the element’s name; it works the same as any input
element that goes inside a formHere are some examples of the <output>
element:
<form> <input type="number" id="b" name="b" value="1" /> + <input type="number" id="a" name="a" value="3" /> = <output name="result" for="a b">4</output> </form>
In this case, the <output>
element is inside a form, so it only needs to have the for
attribute with the list of elements you want that contribute to its value:
<form action="/results" role="search" id="search-form"> <label for="search-input">Search articles</label> <input type="search" id="search-input" /> <input type="submit" value="Submit search" /> </form> <p>There are <output for="search" form="search-form"></output> articles</p>
Here the<output>
element is outside of the form, so it’s preferable to use not just the f
or attribute, but also the form
attribute to create a relation between the live region and the form.
This element is very useful and some people have even used it to create live regions that you’d normally make with role="status"
or role="alert"
. However, keep in mind its screen reader support needs some extra work. If you want to know more, you can check Scott O’Hara’s article about the output
element.
Now, with all possibilities for ARIA-live regions explained, it’s time to investigate what options are available to make live regions in modern JavaScript frameworks!
Here I’m going to show you how to create live regions using the three most popular frontend frameworks: React, Angular, and Vue. After some research, I’ve decided to use three dependencies – one for each framework:
LiveAnnouncer
from Angularreact-a11y-announcer
from ReactHere’s the scenario for our demonstration. We’ll have a <button>
that will let us end a shopping process. When the user clicks the <button>
, the message “Your purchase has been successful!” will appear inside a live region.
But first, I want to set a couple of ground rules:
polite
and assertive
live regions, so I’ll evaluate the markup that the tools generateWith that explained, let’s dive in!
LiveAnnouncer
– AngularAngular offers the LiveAnnouncer
package in its Component Dev Kit to create live regions. I started by adding this markup in the app.component.html
file:
<div>
<h1>Aria Live region text with LiveAnnouncer
</h1> <button (click)="announce()">Complete your purchase!</button> </div>
Then, I went to the app.components.ts
file and imported LiveAnnouncer
to create the announce()
function:
import { Component } from "@angular/core"; import { LiveAnnouncer } from "@angular/cdk/a11y"; @Component({ selector: "app-root", templateUrl: "./app.component.html", }) export class AppComponent { constructor(private announcer: LiveAnnouncer) {} public announce() { this.announcer.announce("Your purchase has been successful!"); } }
Let’s check what kind of markup it creates for our live region:
<div class="cdk-live-announcer-element cdk-visually-hidden" aria-atomic="true" aria-live="polite" id="cdk-live-announcer-6"></div>
When a user clicks on the <button>
, the string “Your purchase has been successful!” will be added to the element, effectively announcing the content to assistive technologies:
<div class="cdk-live-announcer-element cdk-visually-hidden" aria-atomic="true" aria-live="polite" id="cdk-live-announcer-6"> Your purchase has been successful! </div>
If we need to create an assertive
live region, we can add a parameter to this.announcer.announce
to specify that we want an assertive
live region:
public announce() { this.announcer.announce("Your purchase has been successful!", "assertive"); }
Here’s the resulting markup:
<div class="cdk-live-announcer-element cdk-visually-hidden" aria-atomic="true" aria-live="assertive" id="cdk-live-announcer-7"> Your purchase has been successful! </div>
LiveAnnouncer
approaches the task of creating live regions by adding the attributes aria-atomic="true"
and aria-live
(polite
or assertive
) depending on your needs.
This approach will work for assistive technology, but as I mentioned before, it’s preferable to use more appropriate ARIA roles like status
or alert
because they add more context to assistive technology users. Just to give an example, the alert
role will be effectively announced as an alert message by NVDA by narrating: “Alert. Your purchase has been successful!”
I am probably missing more settings, but as far as I have found, those are the options we receive with LiveAnnouncer
and it works! Personally, though, I’d prefer to use custom markup just to be sure a screen reader user doesn’t miss any important context when a live region appears.
Live Utils is part of the vue-a11y-utils
library. It is composed of two utils called Live
, creating a context where it adds the live region and a function called useLive()
that creates the live region.
I started by creating a single-file component called LiveExample.vue
:
<template> <h1>Vue live region example with Live Utils</h1> <button @click="announce(message, isAssertive)"> Complete your purchase! </button> </template> <script setup> import { ref } from "vue"; import { useLive } from "vue-a11y-utils"; const message = ref("Your purchase has been successful"); const isAssertive = ref(false); const [announce] = useLive(); </script>
In this component, I used useLive
to create the live region when the user clicks the <button>
. The function has two parameters: a message
and a Boolean value that determines if the live region is assertive
(if it’s true
) or polite
(if it’s false
).
Next, I imported the component to the App.vue
file. I needed to create the context where the live region will be shown, so I had to call the component Live
to make it work:
<template> <Live> <live-example /> </Live> </template> <script setup> import { Live } from "vue-a11y-utils"; import LiveExample from "@/components/LiveExample.vue"; </script>
This is where I faced my first problem because, in Vue a11y utils’ documentation, this context appears as <VueLive>
, not <Live>
, but if you try to use VueLive
, you’ll see the error message in your console:
“export 'VueLive' (imported as 'VueLive') was not found in 'vue-a11y-utils' (possible exports: FocusTrap, Live, ariaToAttrs, directiveAria, genId, getTabindexByRole, useGlobalHotkey, useHotkey, useLive, useTravel)”
Well, this error is not hard to fix. Let’s get straight to the point and check the live region output:
<div style="position: absolute; height: 1px; width: 1px; margin: -1px; clip: rect(0px, 0px, 0px, 0px); overflow: hidden;"> <div role="log" aria-live="assertive" aria-busy="false"></div> <div role="log" aria-live="assertive" aria-busy="false"></div> <div role="log" aria-live="polite" aria-busy="false"></div> <div role="log" aria-live="polite" aria-busy="false"></div> </div>
This output has quite a lot of problems!
First, the rules inside of the style
attribute are there to hide this container visually but not from screen readers. It is a good accessibility technique to hide certain elements with semantic values, like headings. However, with live regions, this strategy is debatable. In most cases, you’d also want to create visual feedback.
Second, for some reason, this container has four live regions and there are two copies of each one. This might have something to see with the isBusy
attribute that the documentation mentions. However, since the same documentation mentions it’s experimental, I won’t take it into account. It feels unnecessarily redundant.
Finally, those live regions include the log
role, which is only useful for some very specific cases due to its implicit value of aria-atomic="false"
. Personally, I think this tool should have tried to create live regions with more flexible use cases.
In this example, If the assertive
attribute is false
, the messages will be injected into the third live region (the one with the aria-live="polite"
attribute) when the user clicks the <button>
. If the assertive
attribute is set to true
, the first live region will receive the content.
In the most basic sense, this tool works only for some specific cases that do not require visual feedback. I would not use this tool for common cases. I’d prefer to create a custom tool for this task if I’d be using Vue, or maybe use another dependency like vue-announcer
. I decided to go with Live Utils for this article because it appeared to have more recent updates, but the results are not ideal for common use cases.
react-a11y-announcer
– Reactreact-a11y-announcer
was created by Think Company. It creates a component called Announcer
where you add the content of the live region. It’s quite easy to implement, you just need this component and a useState
.
The documentation still uses a class syntax. This is OK (meaning it will work), but by modern standards, it just feels a little outdated. Instead, I decided to create this example with a more modern syntax.
Here’s the code I used in my App.jsx
file:
import Announcer from 'react-a11y-announcer';
import { useState } from "react";
export default function App() {
const [announcement, setAnnouncement] = useState("")
const handleClick = () => {
setAnnouncement("Your purchase has been successful!")
}
return (
<div className="App">
<h1>react-a11y-announcer
test</h1> <button onClick={handleClick}>Complete your purchase!</button> <Announcer text={announcement} /> </div> ); }
Here I added the live region’s content in a useState
, created a function to change that state, and then added it to the Announcer
component with the prop text
.
Alright, let’s check the live region’s output:
<div aria-atomic="true" aria-live="polite" style="position: absolute; visibility: visible; overflow: hidden; display: block; width: 1px; height: 1px; margin: -1px; border: 0px; padding: 0px; clip: rect(0px, 0px, 0px, 0px); clip-path: polygon(0px 0px, 0px 0px, 0px 0px, 0px 0px); white-space: nowrap;"> <p>Your purchase has been successful! </p> </div>
Just like Live Utils from Vue, react-a11ly-announcer
creates a visually hidden live region, which is not ideal for the reasons I previously explained. But unlike Live Utils, it doesn’t have a prop of any kind to change if the live region is polite
or assertive
.
react-a11ly-announcer
created a live region that is polite
by default and (according to the documentation) can’t be changed. So, according to my initial rules for this project, I’d have to say this dependency might not be the best one to do the work.
However, after experimenting a bit, I noticed that you can add more props to the Announcer
component. You can add your desired role
, attributes, and even a className
that will remove the style
attribute, making it visible. So, in theory, if I wanted, for example, a live region with the alert
role, I could do this:
<Announcer text={announcement} role="alert" aria-live="assertive" className="status" />
Remember, it has the attribute aria-live="polite"
by default, so you need to change it manually. This workaround solves our issue, but I have two problems with that:
aria-live="assertive"
attribute (since I’m adding a role="alert"
by myself)You can fix the mistakes react-a11y-announcer
has by default, but it still doesn’t feel very intuitive. If you need to use it for the specific case of creating a polite
live region that doesn’t need to be shown on screen, then use this package. Otherwise, you’d be better off creating it on your own.
My intention with this article is not by any means to denigrate people’s efforts to build tools that use ARIA-live regions to help create more accessible applications. However, as we saw in this article, there’s lots of room for improvement.
When you create accessible applications and use any external package, it’s very important to test the results and ensure the package suits your use case. This is only possible if you have a fundamental understanding of what you want to test. I hope this article has provided some insights into ARIA-live regions and how to build one according to your specific needs.
There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.
LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowHandle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
Design React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.