Accessible status messages with Vue Announcer v2

While accessibility is of course more than "making a website compatible with screen readers", this article is about the challenges developers face when dealing with the dynamic nature of web apps – and tools at their disposal to improve the experience of all users.

What's the deal?

To understand the special challenges that screen reader users face, let's talk a bit about how this form of assistive technology works. Those programs are capable of transforming content that is on a screen into either speech or braille output – if the programmers and developers have used meaningful elements, alternative content for media, and controls communicating their name and purpose when building their interfaces. And the same goes for the browser and the web: "meaningful UI elements" translates here to semantic HTML, a sound document outline, multimedia content that can be perceived by everyone. A screen reader's "focus", its "virtual cursor" can be at one place of the document (in this case, the DOM) at once. It is comparable to reading a book page with you index finger helping you read – it can be in one place at a time.

Now one characteristic of client-side rendered web-apps is their dynamic DOM. Parts of it could and will it get updated, added and removed. All of this in an elegant, reactive and asynchronous way, to delivery that "app-y" experience – but without a page reload. Alas, this causes a problem for screen reader users. Imagine that they are on the last third of a document (their screen reader's virtual cursor is in the footer section for example) and a part of the document's header gets updated. Or imagine another scenario: in a single page application (SPA), a screen reader user is currently in the main navigation. They click the link to the "About" page – and because it is a SPA, only parts of the dynamic document, the content area, will get updated. But if the screen reader stays silent after the interaction with the "About" page, a user is in doubt where their click worked, and they have to actively go to the content area to check. Compare that to the experience of clicking a link and knowing the interaction has worked, because a new page loads and its page title is announced!

A tool against the confusing silence

This is why WAI (W3C's Web Accessibility Initiative) established a concept called "live regions" some years ago. Live regions give web developers the chance to programmatically send announcements to screen readers, regardless of where its virtual cursor is currently located in the DOM. This way, there is an item in a web developer's toolbox that could help with the irritating silence after dynamic DOM updates. Needless to say, with great power comes great responsibility: If the "natural" flow of the SR is disturbed too often or too brutally, the helper instrument becomes a source of great annoyance. Therefore, live regions must be used very reasonably – a means to fill a potentially irritating silence - and therefore, WAI offered two general modes of announcements:

  • The first is named "polite" and is exactly that. Just like a polite human being does not interrupt you mid-sentence in a dialogue only because they have something to say, a polite live region waits until the currently active announcements have finished. Remember: "currently active" has most of the time something to do with the position of the virtual cursor in the DOM.
  • The second one is more suitable for error messages and really important things. If your house is on fire, a polite person that waits for their turn in the dialogue, but to then yell "GET OUT! THERE'S A FIRE!" would be really inappropriate considering the emergency. This type of live region is called an "assertive" one, and it interrupts the screen reader right away.

Hands-on: The code

If you add the aria-live attribute an element (visible or not) it will become a "notification container". Screen readers detect aria-live regions and read their content as soon as it changes. Therefore, you can use this announcement tool to provide hints that are visually apparent to screen reader users. For example, that one particular product has been added to their shopping cart after they interacted with a button labelled "Add to cart" in an online store. A simple example:

  1. Once your app mounts or your page loads, you make sure that an element like the following exists:
<div id="info" aria-live="polite"></div>
  1. On click, this product will be added to the cart, but without a page reload. This would be somehow perceivable for sighted users – maybe the label of the shopping cart changes from "(0)" to "(1)".
  2. Your click handler for aforementioned "Add to cart" button could look like this (just for the example only one button is present):
document
    .querySelector('#add-to-cart')
    .addEventListener('click', () => {
        // Your product-adding business logic here

        // Announcing success with aria-live
        document
            .querySelector('#info')
            .textContent = 'Product was added successfully to your shopping cart';
});
  1. This way, when a product gets added, it is not only visually perceivable but also will be announced by screen readers.

So what happened here? By establishing a live region you told screen readers: "Hey! Observe this element over here for changes regarding its text content. If you notice a change, break out of the normal screen reader behaviour, and announce the new text. In order to determine how "brutal" this announcement should be, look into the value of the aria-live attribute". After that, you are sending notifications via this live regions in case of events that otherwise would require screen reader users to search the site manually for changes.

This is a very low key introduction to aria-live, which only scratched the surface. If you want to learn more about aria-live regions in general, visit MDN web docs.

Vue Announcer

Now it would be easy to convert the code above into Vue, thanks to the fact that reactivity is the name of the game for modern JavaScript Frameworks. But what is also quite modern is to think "why reinvent the wheel if there's already a module around". And there is: vue-announcer.

Just some days ago, vue-announcer version 2 was released. And here's how to install it:

  1. Add it to your project with either npm install -S @vue-a11y/announcer or yarn add @vue-a11y/announcer.
  2. Then, import it in your App.vue: import VueAnnouncer from '@vue-a11y/announcer'
  3. Register it as a Plugin: Vue.use(VueAnnouncer)

When it comes to usage, vue-announcer consists of two general parts:

  • The placement of the live region element (the one that has this special observation deal with screen readers). This is as easy as placing <VueAnnouncer /> anywhere in your app.
  • The usage of this live region from within components. Since vue-announcer was installed as a plugin, it is accessible everywhere in your app with this.$announcer.

Now I wrote above about the importance of the "announcement brutality". This is why, with version 2, there is now a politeness setting, and the API can be used in two ways. So for your really important status messages, you could use either: this.$announcer.set('Something went wrong', 'assertive') or the shorter version this.$announcer.assertive('Something went wrong'). The same goes for polite messages, respectively: Either this.$announcer.set('A thing happened', 'polite') or this.$announcer.polite('A thing happened').

Another good use case, aside from the "shopping cart" example above is to politely announce route changes (read my article about accessible routing – and the importance and limitations of live regions in it).

Now you might wonder why the big intro talking about screen ready theory usage when the actual usage of vue-announcer is more or less two lines of code? There are many reasons for this:

  1. One strength of the modern tooling and thinking in components is that you can hide complex (and accessible) things behind an elegant "developer interface" like vue-announcer does.
  2. Using ARIA features without understanding them is a big problem. They are so powerful that they can even decrease the accessibility of an app or website if used wrong. Consider them a bunch of very sharp knives – powerful if you know how to use them correctly, but also with a high potential of causing pain if misapplied. So writing a short article just explaining vue-announcer's API but not painting the "bigger context picture" would have felt wrong.

To help you deepen your learning about vue-announcer and live regions in general, here is a bunch of hopefully helpful links:

1 If possible, always test with real screen readers and more so, real screen reader users.