I was doing some improvements for internal design system and thought that loading a page with an notification wrapped in role=”alert” would announce the alert automatically to screen-readers. It didn’t work like that.
This is one of the common problems when we just assume and don’t test. When I tested it with NVDA it didn’t work as expected, I only heard “alert”. Which got me to do some more research and especially more testing with multiple screen-readers. Short answer – we have to inject the text into the container with role of alert dynamically, with JavaScript.
Use cases for alert on load
Sometimes we need to alert users of important or critical things on our page or in our application. Like for example when there is a fatal error in the back-end that prevents critical functionality but the website itself still works. It can probably lead to marketing abusing this, but think of all cases when blind screen-reader users must get the message before they continue using the website or web app.
Warning: Because of its intrusive nature, the alert role must be used sparingly and only in situations where the user’s immediate attention is required.
MDN web docs about sparingly using alert (opens in new window).
Possible solution – re-inject content with JavaScript
We need to get the content and re-inject it into same role=”alert” container for screen-readers to announce it. It’s vital that alert container is in the DOM from before or it may not work in all screen-reader and browsers. It’s trivial for JavaScript to get the original content, clear the container and inject the original content in it with a slight delay.
On a side note, this is another example of how JavaScript is actually enabling accessibility as it wouldn’t be possible to do it without JavaScript.
So basically – the solution is to inject the text dynamically, after page is loaded.
I found about that in a GitHub discussion where somebody suggested to add a new aria-live value (opens in new window) that would announce the alert on page load, but it wasn’t accepted because it’s easy to fix it with JavaScript…
Example HTML, CSS and JavaScript code:
<div class="notification" role="region" aria-label="Notification">
<div role="alert" id="js-repeater">
<h2>Warning!</h2>
<p>This is a test notification.</p>
</div>
<button type="button" class="close-notification">Close</button>
</div>
<style>
.notification.closed{
display:none;
}
</style>
<script>
const notificationButton = document.querySelector('.close-notification');
const notificationRemoval = document.querySelector('.notification');
const notificationRepeater = document.querySelector('#js-repeater');
if (notificationButton) {
notificationButton.addEventListener("click", function() {
notificationRemoval.classList.add('closed')
});
}
if(notificationRepeater){
const cache = notificationRepeater.innerHTML;
setTimeout(function() {
notificationRepeater.innerHTML = '';
notificationRepeater.innerHTML = cache;
}, 300);
}
</script>
You can run this code via JSfiddle (opens in new window).
I know, it seems hacky because of setTimeout, but it really needs to be updated otherwise it don’t work consistently.
This was tested with latest versions of:
- NVDA + Firefox,
- NVDA + Chrome,
- Narrator + Chrome,
- Narrator + Edge,
- TalkBack + Chrome on Android,
- VoiceOver + Safari on iPhone.
I didn’t have Jaws available but it should also work there. You should anyways not just “copy paste forget” – please test your implementation with screen-reader and browser combinations relevant to your user base (the more the better).