Bye, live regions. Hello, ariaNotify()
Table of contents (inline)
Introduction
Hello, ariaNotify() method!! The title might be a clickbait, because we are far from replacing live regions, buuut... I am super excited about this new feature. I have been waiting for it since 2024 when it was announced in the WAI-ARIA 1.3 draft and it finally becomes baseline, in September of this year (2026) having complete support on Chrome, Firefox, Edge and Safari. So, you might be wondering why the hype, let's find out.
What is ariaNotify()? It is a web API's method that was presented as a programmatic way to trigger screen reader announcements. Yes, same thing as live regions, but the main difference is that live regions are attached to a DOM element and to achieve this now we have to inject hidden text into a DOM element where the change lives, therefore it only announces a text when there is a structural modification on the content. On the contrary, ariaNotify() queues the text to be announced by screen readers using JavaScript.
Markup of Live Regions
A summary of how live regions works nowadays. They need a combination of HTML + CSS + JavaScript to work properly.
<!-- an empty DOM element -->
<p role="status"></p>
// Then, injects the upate on the empty DOM element
const region = document.querySelector('[role="status"]')
region.textContent = '3 results'
Roles
role="status": it is used for text that are advisory and not important enough to interrupt. It impliesaria-live="polite"andaria-atomic="true".role="alert": it is used for important and/or time-sensitive text and it interrupts what the screen reader is currently saying. It impliesaria-live="assertive"andaria-atomic="true".role="log": it is used for new entries in order, like a chat or live updates. It impliesaria-live="polite"
WAI-ARIA
aria-live: it has three values, polite which waits until the user is idle, assertive which interrupts what the user is perceiving, off which indicates that the changes on a region will only be announced when the user focuses on it.aria-atomic: its value can be true which means that the whole region will be announced when any part of it changes, or false which means that only the part of the region that changes is announced, for example, a clock going from 17.33 to 17.34, only the "34" will be announced.aria-busy: it is used on live regions that might be modified to let the user know when they have been modified. Its value can be true which tells the screen reader that it may wait until the changes are finished before announcing them, or false which means that nothing happens and nothing will be announced until the task is finished.aria-relevant: it is an optional attribute of live regions and it is just a suggestion to assistive technologies. Its values can be additions which refers to element nodes that are added to the accessibility tree within the live region, all, which it is a shorthand foradditions removals text, removals which refers to any text content within the live region that is removed from the accessibility tree, or text which refers to text content or a text alternative that is added to any descendant in the accessibility tree of the live region.
Markup of ariaNotify()
Basically, it is a web API method that can be attached to the Document or to an Element via JavaScript to let screen readers know what they have to announce, where and how. We are not going to need neither to use CSS to hide the announcement from the visual order, nor WAI-ARIA to force announcements.
- Host
element or documentIt is the element that will contain the announcement. It could be the Element in the accessibility tree, or the Document.
- Method
ariaNotify()The method itself. It is called on the host and it contains the announcement and the options.
- Announcement
'Draft saved.'Required. It is the announcement text. Without it there is a TypeError.
- Options
{ priority: 'high' }Optional. If it is omitted or a value that is not on the dictionary is being placed, the value by default would be 'normal'.
- Priority
priority: 'high' | 'normal'normal, the default, is spoken after what is being spoken; high interrupts it.
Parallels with Live Regions
| In Live Region | In ariaNotify | Parallelism |
|---|---|---|
role="status", aria-live="polite" | { priority: 'normal' } (the default) | Both are polite regions and wait until the previous announcement has finished before announcing. |
role="alert", aria-live="assertive" | { priority: 'high' } | Both interrupt what is being spoken to announce the latest message. |
element.textContent = 'Draft saved.' | element.ariaNotify('Draft saved.') | The message to be announced. |
aria-atomic="true" | announcement text | aria-atomic makes a whole region announceable when any part of it changes. In live regionsm it is applicable to the hidden string to announce but in ariaNotify the announcement text is already the argument to announce |
Live Region
<p role="status" aria-live="polite" aria-atomic="true"></p>
<p role="alert" aria-atomic="true"></p>
const polite = document.querySelector('[role="status"]')
const assertive = document.querySelector('[role="alert"]')
polite.textContent = 'Draft saved.'
assertive.textContent = 'Could not save the draft.'
ariaNotify
<button type="button" lang="en">Save draft</button>
const element = document.querySelector('button')
element.ariaNotify('Draft saved.')
element.ariaNotify('Could not save the draft.', {
priority: 'high',
})
In my opinion, the ariaNotify arrived to simplify the knowledge around announcements. In the past, having to communicate the knowledge about live regions was the biggest challenge for accessibility experts so having a method that with a single line of code covered the screen reader announcements without without hardcoding hidden text is refreshing.
Live Regions vs ariaNotify()
In this section, we will learn with use case examples how ariaNotify() simplifies the work and meets the WCAG success criterion to be compliant.
Upload progress
The W3C introduced the Technique ARIA27 where they propose the use of ariaNotify() method to convey the status of a progress bar to meet the Success Criterion 4.1.3 Status Messages (Level AA).
Press Start upload.
Passed to the browser
- Nothing yet.
<label for="bar">Upload of report.pdf</label>
<progress id="bar" max="100"></progress>
<p role="status" aria-live="polite" aria-atomic="true"></p>
<p role="alert" aria-atomic="true"></p>
const polite = document.querySelector('[role="status"]')
const assertive = document.querySelector('[role="alert"]')
polite.textContent = 'report.pdf, 50% uploaded.'
// The upload failed: interrupt.
assertive.textContent =
'Upload failed at 60%. Press Start upload to try again.'<label for="bar">Upload of report.pdf</label>
<progress id="bar" max="100"></progress>
const element = document.querySelector('progress')
element.ariaNotify('report.pdf, 50% uploaded.')
// The upload failed: interrupt.
element.ariaNotify(
'Upload failed at 60%. Press Start upload to try again.',
{ priority: 'high' },
)As you can see, ariaNotify() directly attaches the announcement on the progress element instead of creating a DOM element with HTML, then hiding it from the visual representation with CSS, and finally injecting text with JavaScript, as we had to do in the past.
Wait for a report to be generated
Passed to the browser
- Nothing yet.
<button aria-disabled="true">Generate report</button>
<progress aria-label="Generating the report"></progress>
<p role="status" aria-live="polite" aria-atomic="true"></p>
const region = document.querySelector('[role="status"]')
region.textContent = 'Generating the report.'
// When the report is ready:
region.textContent = 'Report ready: q3-report.pdf, 2.4 MB.'<button aria-disabled="true">Generate report</button>
<progress aria-label="Generating the report"></progress>
const element = document.querySelector('button')
element.ariaNotify('Generating the report.')
// When the report is ready:
element.ariaNotify('Report ready: q3-report.pdf, 2.4 MB.')Cart
Shop
- Tomato soup€2.50
- Sourdough loaf€4.20
- Olive oil€9.90
Your cart
Your cart is empty.
Total: €0.00
Passed to the browser
- Nothing yet.
<h2>Your cart</h2>
<p role="status" aria-live="polite" aria-atomic="true"></p>
const region = document.querySelector('[role="status"]')
region.textContent = 'Tomato soup added. 2 items, total €6.70.'<h2>Your cart</h2>
const element = document.querySelector('h2')
element.ariaNotify('Tomato soup added. 2 items, total €6.70.')Copy to Clipboard
npm install @mica/ui
Passed to the browser
- Nothing yet.
<button>Copy command</button>
<p role="status" aria-live="polite" aria-atomic="true"></p>
<p role="alert" aria-atomic="true"></p>
const button = document.querySelector('button')
const polite = document.querySelector('[role="status"]')
const assertive = document.querySelector('[role="alert"]')
button.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText('npm install @mica/ui')
polite.textContent = 'Copied to clipboard.'
} catch {
// It did not happen: interrupt.
assertive.textContent =
'Could not copy. Select the command and press Control C.'
}
})<button>Copy command</button>
const element = document.querySelector('button')
element.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText('npm install @mica/ui')
element.ariaNotify('Copied to clipboard.')
} catch {
// It did not happen: interrupt.
element.ariaNotify(
'Could not copy. Select the command and press Control C.',
{ priority: 'high' },
)
}
})Search Results Announcement
12 teas.
- Assam
- Darjeeling
- Earl Grey
- Green sencha
- Jasmine
- Lapsang souchong
- Matcha
- Oolong
- Peppermint
- Rooibos
- White peony
- Yerba mate
Passed to the browser
- Nothing yet.
<input type="search">
<p id="count">12 teas.</p>
<p role="status" aria-live="polite" aria-atomic="true"></p>
const input = document.querySelector('input')
const region = document.querySelector('[role="status"]')
let timer
input.addEventListener('input', () => {
clearTimeout(timer)
timer = setTimeout(() => {
region.textContent = '3 results for “tea”.'
}, 600)
})<input type="search">
<p id="count">12 teas.</p>
const input = document.querySelector('input')
const element = document.querySelector('#count')
let timer
input.addEventListener('input', () => {
clearTimeout(timer)
timer = setTimeout(() => {
element.ariaNotify('3 results for “tea”.')
}, 600)
})The count on the page follows every key. The call waits 600 ms after the last one, so a word typed quickly is said once.
Checkout Summary
Order summary
- Subtotal
- €40.00
- Discount
- €0.00
- Shipping
- €3.90
- Total
- €43.90
Passed to the browser
- Nothing yet.
<h3>Order summary</h3>
<p role="status" aria-live="polite" aria-atomic="true"></p>
const region = document.querySelector('[role="status"]')
region.textContent =
'Code SAVE10 applied: €4.00 off and free shipping. ' +
'Total is now €36.00.'<h3>Order summary</h3>
const element = document.querySelector('h3')
element.ariaNotify(
'Code SAVE10 applied: €4.00 off and free shipping. ' +
'Total is now €36.00.',
)Scope: Document or Element
The whole page
Online.
The button stands in for the browser’s offline and online events.
Invoices
Invoice 1042
UnpaidFactura 1043
Sin pagar
Passed to the browser
- Nothing yet.
<p role="status" aria-live="polite" aria-atomic="true"></p>
<p role="status" aria-live="polite" aria-atomic="true"
lang="es"></p>
const [page, invoice] = document.querySelectorAll('[role="status"]')
page.textContent =
'You are offline. Changes are saved on this device.'
invoice.textContent = 'Factura 1043 marcada como pagada.'<h3 lang="es">Factura 1043</h3>
const element = document.querySelector('h3')
// About the whole page: document.
document.ariaNotify(
'You are offline. Changes are saved on this device.',
)
// About the invoice: its heading, which carries lang="es".
element.ariaNotify('Factura 1043 marcada como pagada.')Should I Document or Should I Element?
Neither of the MDN page for ariaNotify() in Document nor the MDN page for ariaNotify() in Element make a clear distinction between whether to attach the ariaNotify method to the Document or to the Element, but we can state that the choice is about the message per se. Based on that, I created the following table to help decide where to attach the method based on semantics and context.
Another thing worth mentioning is that the language of the announcement by default is, it comes from the lang attribute on the html element. If you want a dedicated section of the page to have a different language, set it explicitly.
| Situation | Where | Why |
|---|---|---|
| Announcement about connection, a new version, session update, and a total upate | Document | The announcement is not tied to an action by the user or to an update in a region, but to a global update that improves or modifies the experience. |
| Announcement about a thing happening in a section of the page. It could be a count, a total updated, a progress bar, etc | Element | The announcement has a context, a scope |
| Announcement of something that has been removed from the DOM | Document | The Element does no longer exists in the DOM, so the announcement should be made from the Document |
Reusable and Global Solution for Announcements Using Just ariaNotify()
In my lib of methods, last month I implemented a reusable function to dispatch announcements programmatically using the ariaNotify() method.
- A function to check if the native HTML element supports
ariaNotify()export const hasAriaNotify = () => typeof Element !== 'undefined' && 'ariaNotify' in Element.prototype - Create a type (if you are using TypeScript) to tell TypeScript the method may exist and what it takes.
type Notifier = { ariaNotify?: (announcement: string, options?: { priority: Priority }) => void } - Then we have to create a
notifyfunctionexport function notify( from: Element | Document, message: string, priority: Priority = 'normal', ) { const native = hasAriaNotify() const send = () => { (from as unknown as Notifier).ariaNotify?.(message, { priority }) } if (native) return send() }- The function has 3 mandatory parameters:
fromwhich checks if the method is being attached to an Element or Document,messagewhich is the string of texts to be announced andprioritywhich is the type of announcement that the screen reader should do. - If
nativeis true, we are going to attach the method to the Element/Document and dispatch the action.
- The function has 3 mandatory parameters:
- The reusable function can be used in the following way:
notify(bar, 'report.pdf uploaded.')
Screen Readers announcements
Chrome + VoiceOver
Safari + VoiceOver
Chrome + JAWS
Chrome + NVDA
Firefox + NVDA
WCAG 2.2 Success Criterion - Level AA
| Success Criterion | Explanation |
|---|---|
| 4.1.3 Status Messages (AA) | as in Technique ARIA27, the progress should be announced. |
| 3.3.1 Error Identification (A) | For example, a failed upload should be announced with high priority |
Browser Support
According to MDN, this is the list of browsers that support ariaNotify on their latest update since September 2026. Oldest versions might not support ariaNotify.
- Chrome version 141
- Edge version 141
- Firefox version 150
- Opera version 125
- Safari version 27
- Chrome Android version 141
- Firefox Android version 150
- Opera Android version 93
- Safari on iOS version 27
- Samsung Browser version 30
- WebView Android version 141
- WebView on iOS version 27