Why this is worth noting
A frequent feature of a web-app is to load data asynchronously, e.g. from an internal or external API, or database. This leads to loading states that you, I assume, convey visually with nicely animated progress bars. This is all well and fine for users who use your application visually, but can lead to screen readers just remaining silent. In these loading states, nothing obvious happens for them. Let's find a strategy to change that.
A possible solution
The logical first step is to search for an HTML element that helps us transport our message in a semantic way. What we find is the
<progress> element, which, by MDN's definition...
...displays an indicator showing the completion progress of a task...
Nice, exactly what we were aiming for. Further research shows that authors should use the following attributes to supply further meaning and information to screen readers:
aria-valueminis the minimum value, the – so to speak – starting point of the progress
aria-valuemaxis the end value
- To convey current progress to screen readers, use
aria-valuenowand update it automatically
Enhancing the progressbar
So we're done here, right? Wrong. Using
progress element alone is not the worst thing you could do, but there are always way to improve. If you think, for example, that the presence of a
<progress> bar alone leads to automatic screen reader output, when it updates – nope, that is not the case. A screen reader's virtual cursor has to be placed on the progressbar in order to hear its information. One way to ensure that is focus management. Therefore, we have to add
role="progressbar" seems redundant at first, but is because of the JAWS screen reader, which otherwise does not recognize the element as a progressbar. In total, this leads to the following code for a progressbar that its progress on the range of 0% to 100%:
<label for="loading">Progress:</label> <progress id="loading" value="42" aria-valuemin="0" aria-valuenow="42" aria-valuemax="100" max="100" role="progressbar" tabindex="-1" >42%</progress>
<label for="loading-progress">Loading progress:</label> <ProgressBar id="loading-progress" :value="value" />
This would – and will – be the code in Vue.js. One could even design this component a little bit more concise, using the label text as a prop and putting the label element in the ProgressBar component itself, but that is a topic for another article.
Dealing with focus management
To setup a sensible focus management, imagine the following exemplary user flow:
- A user clicks a button, thus triggers a loading/fetching sequence
- While this button disappears, a progress bar component will be shown. This component will be dynamically updated
- When the loading succeeds the progress bar disappears and the loaded content is finally shown
Now, it would be resonable to establish the following focus flow, helping screen reader and keyboard-only users:
- After the interaction with the "load more" button focus goes to the progress bar (which has been made focussable, see above)
- While focus is on the progress bar the current progress will be read out in screen readers. This way, their users are informed about the current state of the pause
- After the loading is complete, the progress bar if full, and it disappears. In order to not lose focus at the same time, focus will be transmitted to the newly loaded content, or its container, right before.
This video shows what is described above. I visualized the focus with a special indicator (this indicator is also available in the Vue demo app as "Accessible App Debugging" mode).
Using that strategy in Vue
We start with the component itself (See the source code). In it,
value are props, whereas only the latter is mandatory (since it conveys the current progress), and
max can default to 0 or 100, respectively.
Then we look at the component or page that uses the progress bar. This part of the code should actually start some form of loading progress (or other interstitial state). In the case of the demo app, this is the past order page, and we're faking a database lookup of one's past orders.
If you look into the relevant component,
Orders.vue you will find the following things:
$refs. The one on the
<h1>is part of the accessible routing and existed before, but we're also referencing the container where we'll eventually send the focus to (when the progressbar has done its job and disappars. Note also the
- Hiding and showing with
v-ifdirectives, partly for demo reasons, partly to react, when state is updated and our fake database query is finished
- Most importantly, we're passing the current "state of the upload" via prop to the
<ProgressBar>component. Fetching or querying is faked here with a simple setInterval.
- The actual focus setting is done via
§refs(see above), and with both
Vue.$nextTickcallbacks and a certain delay for screen readers (around 1000ms is usually recommended).
- The example app's order page. Click on "Click here to query our database for your past orders"
- Source code of the ProgressBar component
- Source code of the "Orders" view
- Use a progress bar with all necessary attributes (
role="progressbar" for JAWS,
- Make the progressbar programmatically focusable with
- Create a focus management strategy that makes sense in your particular app. Ensure focus will be set to the progressbar, and make sure focus is not lost after successful loading (or whatever you created the interstitial view for).