How to label a loading animation for WAI-ARIA? - html

I'm working on fixing some accessibility issues on a web page. I have this div that acts as a dialog, and inside at one point a div containing a loading animation and a text "Working..." is displayed.
I am unsure as to how to label these two items in order to correctly notify the blind user that there is a progress animation and that it's working and he should wait.
<div id="loading" style="display: none;">
<div class="mgBot15"><span class="txt16" role="alert">Working...</span></div>
<img src="loading.png" role="progressbar" aria-busy="true"/>
</div>
I tried adding the role and aria-busy properties to the img (also to the parent div, at first).
When this div appears (by changing the display style property), it correctly reads "Working..." but I hear no indication that it's busy and that the user should wait, am I missing something?
I've looked all over for examples for loading animations, to no avail so far.
Note: I'm using NVDA as a screenreader to test.
Thanks

The best solution I could come up with was using role alert, and aria-busy="true".
<div id="loading" style="display: none;">
<div class="mgBot15"><span class="txt16" role="alert" aria-busy="true">Working...</span></div>
<img src="loading.png" alt="loading" />
</div>

I believe the most sensible approach would to use the combo
aria-busy="true" aria-live="polite"
The reason for that is because some pages might have a lot of separate loaders (let's say one for each component, not a single loader for the whole page) and it you use aria-live="assertive" or role="alert" it will be very intrusive and each of the loaders will get called out.

The correct role to use here is progressbar as the original question used. Other roles like alert may work, but they are less specific, meaning assistive technology may present the information in a less ideal manner.
There are a few issue with the original question's example, though:
If you wish to have the text be announced in the same as an alert is, aria-live="assertive" should be used rather than the alert role. That aria-live value is what causes the screenreader to announce the text when it does for an alert.
The text to be announced should be set on the element with the progressbar role using the aria-valuetext attribute. It should not be set solely on a separate adjacent element. If it needs to also be included in another element for presentational reasons, that element should have aria-hidden="true".
Per the spec, aria-valuemin and aria-valuemax are to be specified even when the progress is indeterminate (like a spinning loading indicator). These could be set to 0 and 100 respectively as simple placeholders implying a percentage.
When the loading is complete, the aria-valuenow could be set to whatever was used for aria-valuemax, and aria-busy can be set to false.
This leads to one potential alternative to the original question:
<div id="loading" role="progressbar" aria-valuetext="Working…" aria-busy="true"
aria-live="assertive" aria-valuemin="0" aria-valuemax="100">
<div class="mgBot15" aria-hidden="true"><span class="txt16">Working...</span></div>
<img src="loading.png" alt="" />
</div>

After a day of fiddling with a similar issue, I was able to finally get this working with a lot of reading and experimenting. I'm using NVDA for a screen reader.
<div class="loader" show.bind="isLoading" role="alert" aria-label="Loading"></div>
The following attributes were key: role and aria-label.
Above example makes NVDA announce "Loading alert" once isLoading becomes true. Important to note is that NVDA pronounces the aria-label value, followed by "alert". Using roles "log" or "status" did not end up in any announcement.

Bootstrap used role="status" like this :
<div class="spinner-grow text-primary" role="status">
<span class="sr-only">Loading...</span>
</div>
and in MDN it said :
The status role is a type of live region and a container whose content
is advisory information for the user that is not important enough to
justify an alert, and is often presented as a status bar. When the
role is added to an element, the browser will send out an accessible
status event to assistive technology products which can then notify
the user about it.

There's a good article I came across that explains what needs to be done for this scenario Loading spinner accessibility
The spinner should be included inside the container. Its visibility can be toggled in relation to the aria-busy attribute. They should always be opposites, i.e, if currently loading, section[aria-busy="true"], .tn-spinner[aria-hidden="false"], once the content is loaded, toggle to false and true respectively.

Related

Accessibility error 'Text not included in an ARIA landmark'

I have a button which opens a calendar modal, I know calendars are bad for accessibility but my client insists on it. This is the code that opens that calendar modal.
<div class="col-sm-6 hidden-xs text-right calendar-cta">
<a type="button" onclick="openNav()" href="#" class="btn-primary">Calendar view</a>
</div>
I then have a close button on the model, which is where the accessibility error is being produced. The x is showing up as 'Text not included in an ARIA landmark'. What am I doing wrong? What do I need to add in order for this to stop producing the accessibility error. Any help would be hugely appreciated.
<div id="myNav" class="overlay" role="menu">
<a class="closebtn" tabindex="-1" role="menuitem" aria-label="close calendar view">Ă—</a>
<div class="overlay-content"> </div>
</div>
This is more of a warning than an error. It's not required under WCAG, although it is best-practice, and you should try to do it if you can.
It is a best practice to include ALL content on the page in landmarks, so that screen reader users who rely on them to navigate from section to section do not lose track of content.
https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA11
You should ideally be using HTML semantic sectioning elements, like: <main>, <nav>, <aside>, <header>, <footer>, etc. This warning is saying that all rendered content should be in some sort of containing element that has an ARIA role associated with it.
There's a great chart that maps all of the HTML 5 semantic elements to their implied ARIA roles.
I'd also recommend changing your a.closebtn to a button element and removing tabindex="-1". Since you're not navigating to a different location, but rather doing something that causes a change to the UI, I think that a button is a more appropriate choice. The tabindex attribute isn't necessary and only serves to prevent receiving focus by manually tabbing.
The direct and the short answers to the questions:
What is wrong here is that aria-label does not contain the visible text and it is a concrete WCAG failure under 2.5.3 Label in Name. Why and how it fails is explained under Failure due to the accessible name not containing the visible label text
Two quick and dirty solutions:
Write "close calendar view" instead of "x" if possible and remove the aria-label attribute
Hide the "x" sign from ARIA by putting it inside <span aria-hidden="true">x</a>. The one and only thing the screen reader will read will be "link close calendar view".
The "Text not included in an ARIA landmark" may still show up, it is a false positive when it is hidden from ARIA.
However, the above fix will fix only 2.5.3, but there are more important issues here.
I wonder
Why the role="button" was assigned to an anchor element to make a button, when there is the button element readily available and no link to anywhere was intended.
From Bootstrap:
When using button classes on elements that are used to trigger
in-page functionality (like collapsing content), rather than linking
to new pages or sections within the current page, these links should
be given a role="button" to appropriately convey their purpose to
assistive technologies such as screen readers.
What tabindex="-1" does there. It will remove the button from the tab order and the keyboard user will not be able to reach or operate it. It causes a concrete 2.1.1 Keyboard failure.
Why closing the calendar view button has a role="menuitem". Is there a menu there?
Moreover, the usage of the "x" character as the only visible accessible name of a button is a 1.3.3 Sensory Characteristics failure. It is an assumption that all users know "x" denotes "close". Just like not everyone understands an asterisk denotes "required fields", ">" sign means "next", or 3 bars on top of each other is now a menu called "hamburger menu". These should all have some textual explanation. Aria label makes the explanation for the screen reader users but some sighted users may still fail to understand what is meant there.
Alternative code, not including how the btn-close works: (Close button Bootstrap v. 5.0)
(Please note that Bootstrap does not address the 1.3.3 criterion explained above, that is why I included a tooltip in my suggestion.)
<div class="col-sm-6 hidden-xs text-right calendar-cta">
<button type="button" class="btn-primary" onclick="openNav()">Calendar view</button>
</div>
<div id="myNav" class="overlay">
<button type="button" class="btn-close" aria-label="Close calendar view" title="Close"></button>
<div class="overlay-content"> </div>
</div>

VoiceOver focuses on aria-hidden element

On our (Vue.js v.2) webpage we have a section where you can swipe between cards, or by pressing "left" and "right" button. This swiping section is written with vanilla JS, and no fancy library. In order to have the adjacent visible when swiping, I need to have all of them visible in the DOM tree, but hiding them from the viewer. I have made it so all the unfocused cards are aria-hidden, and it works great when using ChromeVox. The problem is that when using VoiceOver, and I click the "right" button, and immediately tab down to the card, it will read out the card to the left, and also get trapped there since the card is aria-hidden. Code-wise the change from aria-visible to aria-hidden happens as soon as the button is pressed, but it seems like VoiceOver has already decided that when I am focusing on the "right" button, that the next element should be card 5 (for instance). If I wait around 1 or 2 seconds after clicking the "right" button, it will change the "next" element, and focus on the correct one (card 6) when I tab down. Is there any way to get around this, so it will focus only on the aria-visible element? Maybe a way to "force refresh" VoiceOver's stack of elements to read next? Maybe if I remove the message being read out when clicking the button, it will refresh immediately? I still haven't found of doing either of those things. I created a low quality flowchart to illustrate the problem better. What I want is for it to behave like ChromeVox.
I have tried several methods of getting this to work now, and it feels like it's a bug with VoiceOver. Some of the (desperate) attempts I've tried: setting tabindex=-1, role=presentation, changing the ID of "right" button dynamically as I navigate between cards, creating an empty div with a dynamic ID below the button, using aria-flowto, dynamically setting aria-describedby on the "next" element, and different variations between these and some other stuff I can't remember.
Eventually I found a solution that kinda works. I'm not very happy about it, but it's better than nothing. What I did was to make the title inside the card aria-hidden, and creating a currentHeader variable in store. I created an sr-only & aria-visible title above the swiping section, where the v-html points to the currentHeader variable. This way, the "next" element for the "right" button will always be the same element, but content will change after I click the button. It's not a perfect solution, and for some reason it makes VoiceOver "halt" when trying to go to the next element immediately after clicking the button, but at least the user won't read the wrong text and get trapped. Here's a pseudocode illustration of how I did it if my explaination was confusing:
// old solution // old swiping-section
<button id="left" /> <div v-for="element in elements" />
<button id="right" /> <h3 v-html="element.title" />
<swiping-section /> <p v-html="element.desc" />
</div>
// new solution // new swiping section
<button id="left" /> <div v-for="element in elements" />
<button id="right" /> <h3 aria-hidden="true" "v-html="element.title" />
<h3 class="sr-only" v-html="currentHeader" /> <p v-html="element.desc" />
<swiping-section /> </div>
If anyone finds a better way to do it, please post your solution.

What should be the aria-role for the box that can be opened by clicking on notification icon on navbar?

I am trying to implement the notification component that will show the list of the items and will be opened by clicking on the notification icon on the fixed navigation bar on the top. I don't think it's a menu bar. Because the menu provides the actions that can be performed and it can also have a sub-menu.
https://www.w3.org/TR/wai-aria-practices/#menu
Can anyone let me know what should be the aria-role of such kind of components?
Below is the code sample. I will open the template dynamically by clicking on the notification icon button:-
<button aria-label="notifications">
<mat-icon class="mr-md">notifications</mat-icon>
</button>
<!-- Notification template -->
<div class="notifications__item">
Notifications
<li *ngFor="let notification of notifications" class="notifications__item">
<mat-icon class="notifications__icon material-icons-round">
{{ notification.icon }}
</mat-icon>
<div class="notifications__content">
<div [ngClass]="{ 'notifications__warn': notification?.type }">
<span>{{ notification.title }}</span>
</div>
<div>{{ notification.description }}</div>
</div>
<small class="notifications__caption">
{{ notification.duration }}
</small>
</li>
</div>
There are still a lot of things to consider that your example doesn't cover, so this isn't a complete answer, it is just pointing you to the relevant WAI-ARIA depending on what route you take.
The button
The first thing to consider is the button. You need to tell screen reader users what state it is currently in. For this we use aria-expanded to indicate whether the item it controls is currently opened or closed. (aria-expanded="true" for open, aria-expanded="false" for closed.)
At the same time we want to indicate what item this button controls (as the notification list isn't 'owned' by the element - for example if it was an <li> with a nested <ul> in a menu then the list would be 'owned' by it).
For this we would use aria-controls or aria-owns and point it to the ID of the element it controls. For the difference between them see this stack overflow post as a good explanation, in this example I would say it is aria-controls but yet again depends on your implementation and positioning in the DOM.
With regards to the button itself and where it sits in your menu, this is still considered navigation so it should sit within your <nav> element. However if this sits outside of your navigation along with say a 'help' and 'account' section you may consider those items part of a toolbar. (yet again I would say it doesn't apply here but something to look at)
Also it doesn't appear to be applicable here but if you include any links etc. within the 'popup' / modal that shows the notification list (i.e. a 'view all notifications' link), you should consider aria-haspopup="true"
The notification list
Right so we have a button pointed to the container (don't forget to give the container the relevant ID for aria-owns or aria-controls). Next what about the container itself?
Well in this example it appears that the container should be treated like a modal.
So for this reason you need to consider:-
trapping focus in the modal,
close with Escape,
returning focus to the button that activated it on close,
providing a close button that is accessible by keyboard,
a title for the modal (even if it is visually hidden)
What I would recommend is add some of the accessibility features above, try it with a screen reader and keyboard and see if it is easy to use. Once you have decided on your pattern ask some more questions on specific use case issues as the above is general guidance.
A few things to consider based on your markup
Additional things to consider from your example:-
use aria-hidden="true" on your icons, they don't add anything for screen readers (assuming your notification.title is descriptive).
For the notification title consider making it a relevant heading (<h2> - <h6> depending on position in document.
Don't forget to add some visually-hidden text that describes the warning level (I can see you have some form of colouring / distinction in [ngClass]="{ 'notifications__warn': notification?.type }" - expose the same info to screen readers.)
You currently have a <li> within a <div> - maybe change the outer <div> into an <ul> so it is semantically correct (<div class="notifications__item"> into <ul class="notifications__item">)
I hope the above is useful to set you on the right track, a lot to read but after reading the linked articles you should be able to make a better decision on what pattern you are using (as I didn't even mention making this a sub item within your menu) and can then ask some more questions on specific details you don't yet understand.
final thoughts / tips
test with a screen reader - this is the biggest tip I can give on working out how WAI-ARIA works and interacts with things.
Also if you are ever in doubt as to whether a WAI-ARIA attributre is applicable it is better to not include it.
Incorrect use or WAI-ARIA is actually worse than not including it at all so make sure you understand when to use an attribute reasonably well before implementing it. If I am ever unsure (as it still happens to me!) I tend to look at 2 or 3 examples of it in use and see if my pattern fits the examples I looked at.

WCAG - How to make the screen reader read all the page automatically?

I need to have a screen in my app which all of it's content will be read using the screen reader automatically.
I tried to add role="dialog" aria-live="assertive" aria-atomic="true" and it didn't make it.
I also tried to use role="alert" aria-live="assertive" aria-atomic="true" it did read it, but using 'alert' as prefix.
How can I make it happened using no prefixes and additional info ?
I believe the document role is the correct one to use in your case. From MDN:
Generally used in complex composite widgets or applications, the document role can inform assistive technologies to switch context to a reading mode: The document role tells assistive technologies with reading or browse modes to use the document mode to read the content contained within this element.
You should include tabindex="0" on the element in which you wish to be immediately read, and use JavaScript to set the focus to the element.
Update
I tested the following code in Mac/Chrome, version 79.0.3945.88, and removed the tabindex attribute as well as all JavaScript. VoiceOver immediately read the contents of the document in its natural order.
<div class="container" role="document">
<p>
Read this first.
</p>
<p>
Read this next.
</p>
</div>
I did a screen capture of the VoiceOver utility as it was reading the above HTML to show it working on page load (forgive the Giphy Capture bit at the beginning of the gif).
If this really is something like a dialog - which means it consists mostly of operable content (UI controls such as buttons) then the non-operable content will not get announced by default. You can solve this if, on the thing with role="dialog", you have aria-labelledby pointing at its heading, and aria-describedby pointing at all the ids (space-separated) of the elements inside that you want read-out when it opens.
The markup might look like this:
<div role="dialog" aria-labelledby="dialogheading" aria-describedby="foo bar">
<h2 id="dialogheading">Let's have a dialog</h2>
<p id="foo">lorem ipsum</p>
<p id="bar">rhubarb rhubarb</p>
<button>yadda</button>
<button>yoda</button>
</div>
If there's really a lot of non-operable content, or if there's no operable content, role dialog is the wrong thing. If the context for this 'screen' is in forms/application mode use role=document instead, and make sure it has a tabindex so you can give it focus, which should switch the screen reader to browse mode.

Accessibility when data is loaded

I am working on accessibility issue.
I have a spinny/loader which appears on screen when data is getting loaded.
<spinny aria-live="polite" role="alert" aria-label="Loading Page">
So when the spinny appears on screen, screen readers give me alert that spinny is loaded.
Now I want that when the spinny goes away from screen i want the screen reader to provide message such as data loaded or something like that.
I have tried aria-relevant, aria-atomic etc but nothing seems to have worked.
First off, your code sample is specifying conflicting information. Using role="alert" gives you an implicit aria-live="assertive" but you are also specifying aria-live="polite". I would recommend removing role="alert". Having aria-live="polite" is sufficient.
However, if you remove the role from <spinny> (which I'm guessing is a custom html tag?), then your aria-label may not be honored because aria-label'ed things often need a role in addition to the label in order for the label to be read by a screen reader. See "Practical Support: aria-label, aria-labelledby and aria-describedby"
But, I think you might be using aria-label incorrectly anyway. Your live region should look something like:
<div aria-live="polite" class="sr-only" id="myspinny"></div>
(See What is sr-only in Bootstrap 3? for the "sr-only" class. It will visually "hide" the <div> so that any text you put inside it will not be visible to the sighted user but will still be available to screen reader users.)
When data is loading, you should inject text (via javascript) into "myspinny" so that it looks like:
<div aria-live="polite" class="sr-only" id="myspinny">Loading Page</div>
Since the <div> is a live region, the text ("Loading Page") will be announced.
When the data is finished loading and you want to remove the spinner, inject new text into "myspinny" so that it looks like:
<div aria-live="polite" class="sr-only" id="myspinny">Data Loaded</div>
and the screen reader will say "Data Loaded".