Make custom radio control accessible to screen readers - html

I needed to create a radio control for selecting 1 of 12 pre-defined options. User's needed an overview of all possible options at a glance, and to see the full details of each option on hover/focus/select. There's a lot of detail for each option but they can be grouped together easily (AWS EC2 Instance Types).
Here's what I created:
Except for tabindex, :hover, :focus, :active, the interaction is controlled by JavaScript. After simplifying, the markup looks like this:
<div>
<div>
<h3>General Purpose</h3>
<!-- details... -->
<div>
<span>1x</span>
<!-- others... -->
</div>
</div>
<div>
<h3>Accelerated Computing v2</h3>
<!-- content... -->
</div>
<div>
<h3>Accelerated Computing</h3>
<!-- content... -->
</div>
</div>
It works for well-sighted keyboard/mouse users but I'm not sure how to adjust this input for screen readers.
As an additional wrinkle, we should read just the size & hourly price when previewing an option from the same group the user is already looking at, and I'm not sure how to do this either.
NB: You can only click "1x", "2x", etc. these are the only selectable elements, the highlight around the category is just a visual thing.
Thanks for reading! Can you help?

Follow the "Radio Group" pattern on the WAI-ARIA Authoring Practices 1.1. You basically need a role="radiogroup" (with a label) around the entire container (the parent of your 12 <div>s) and then each radio button should have role="radio". (You'll also need aria-checked and possibly aria-label or aria-labelledby on each radio button, but that is explained in the pattern.)
Note that specifying these ARIA attributes only conveys the semantics of the object to screen readers. It does not give you any behavior of a radio group. That is, when the user selects one radio button, it's up to you (presumably via javascript) to unselect the previous button that was selected.
To answer your question about only reading part of the information, you can use aria-hidden on anything you don't want read, or you can specify an aria-label or aria-labelledby which will override any embedded text in child elements. The example below uses aria-labelledby and points to the heading of each radio button so only that heading will be read when the screen reader tabs to the radio button. However, hiding information from screen readers would fail accessibility compliance (WCAG) if the information is available to sighted users. In your screenshot, if the cost per hour, the GPU, and other info is visible to sighted users but hidden from screen reader users, that would be bad.
Note also that you need to manage the keyboard focus yourself (javascript). Only one of the radio buttons should have tabindex="0" and the others should have tabindex="-1". If the user presses the down arrow, the radio button that used to have tabindex="0" should now have tabindex="-1" and the newly focused radio button should have tabindex="0". The aria-checked attribute should match the tabindex so that the button with tabindex="0" has aria-checked="true" and the buttons with tabindex="-1" should have aria-checked="false".
Here's a rough sample using your original code. Note that the radio buttons are using aria-labelledby pointing to the headings, but this is not recommended because the cost per hour, GPU, etc will be hidden from screen reader users.
<div role="radiogroup" aria-label="your 12 options">
<div role="radio" aria-labelledby="GeneralPurpose" tabindex="0" aria-checked="true">
<h3 id="GeneralPurpose">General Purpose</h3>
<!-- details... -->
<div>
<span>1x</span>
<!-- others... -->
</div>
</div>
<div role="radio" aria-labelledby="AccComputing2" tabindex="-1" aria-checked="false">
<h3 id="AccComputing2">Accelerated Computing v2</h3>
<!-- content... -->
</div>
<div role="radio" aria-labelledby="AccComputing" tabindex="-1" aria-checked="false">
<h3 id="AccComputing">Accelerated Computing</h3>
<!-- content... -->
</div>
</div>

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>

Aria-label makes nested content not visible for screen reader

I'm displaying some numeric info to the user describing it by using graphic icon label. I want to make it readable by screen reader using the aria-label tag. However, when I add this attribute, it makes the nested content not visible for screen readers. Below is the structure how the html looks like.
<ul>
<li tabindex="0" aria-label="Counter 1">
<div>
<i aria-hidden="true"></i>
<span class="counter">{{properties.counter1}}</span>
</div>
</li>
<li tabindex="0" aria-label="Counter 2">
<div>
<i aria-hidden="true"></i>
<span>{{properties.counter2}}</span>
</div>
</li>
</ul>
I was trying to use aria-describedby attribute, but it didn't work with dynamically created id.
Below is the screenshot from the chrome dev tools accessibility tab. I wonder why labeling makes content value omitted.
Screenshot
That's correct, specifying aria-label or aria-labelledby generates the accessible name for the element rather than having to nest through all the child elements and concatenating text from each child. All child elements will be ignored. You can see more details regarding child elements at https://www.w3.org/TR/wai-aria-practices-1.2/#naming_with_aria-label. In particular:
When applied to an element with one of the roles that supports naming from child content, aria-label hides descendant content from assistive technology users and replaces it with the value of aria-label.
aria-describedby specifies the accessible description of the element, which is different from the accessible name.
When a screen reader announces an element's name, it will announce the accessible name first, possibly any state information (such as a checked checkbox or an expanded accordion), and then the accessible description.
The accessible name and description calculation has a precedence list which can be seen at https://www.w3.org/TR/accname-1.1/#step2. Essentially it looks in this order. Whichever one is found first is used:
aria-labelledby
aria-label
<label>
internal text (such as the text between <button> and </button> or between <a> and </a>)
Note that there are some limitations to aria-label and aria-labelledby. See https://www.w3.org/TR/using-aria/#label-support

JAWS reads Headings as clickable even though parent div has not click handler associated

I am working on accessibility testing for an Angular project. I have some code for a modal that is as below
<div class="modal fade" bsModal #cancelConfirmModal="bs-modal" tabindex="-1" role="dialog"
attr.aria-label="Cancel Modal" aria-hidden="true">
<div class="md-dialog modal-md">
<!-- Modal content-->
<div class="md-content">
<div class="md-header">
<div class="md-title pull-left">
<h4 tabindex=0 class="headerBottomMarginNone"> Cancel</h4>
</div>
<button type="button" class="close-popup" (click)="hideCancelModal()">
<img src="{{ pathImg }}/close.png" alt="Close Icon"
(mouseover)="changeCloseIconOnHover('close-popup3')"
(mouseout)="changeCloseIcon('close-popup3')" id="close-popup3">
<label class="sr-only">Close</label>
</button>
</div>
<div tabindex=0 class="md-body">
{{ cancelMessageBody }}
</div>
<div class="md-footer">
<button type="reset pull-right" class="ts-btn ts-btn-primary no-btm-margin" (click)="revert()">Yes</button>
<button type="pull-right" (click)="hideCancelModal()"
class="ts-btn ts-btn-tertiary margin-left10 no-btm-margin">Cancel</button>
</div>
</div>
</div>
</div>
When I tab over to <h4 tabindex=0 class="headerBottomMarginNone"> Cancel</h4> it reads Cancel heading level 4 clickable. There is no click event on the parent or even its parent. h4 is made tab reachable as per the QA's preference. How do I stop JAWS from announcing clickable??
Short Answer
Remove the tabindex.
Long Answer
Headings should not have a tabindex (other than maybe a tabindex="-1", covered below).
Anything with a tabindex is considered to be interactive and it is expected that you have supplied the relevant handlers for focus, click, keyboard keys etc.
Your QA is incorrect on this and is making the software harder to use.
The only time it is appropriate to use a tabindex on a heading is if you need to programatically focus it (for example on an AJAX application where you load a new page in it is a good practice to focus the heading level 1 on the page to let screen reader users know the new page has loaded in.) At this point the only tabindex that is appropriate is tabindex="-1" so that it can only be focused programatically and not via the tab key.
Your QA may think that screen reader users need to be able to focus the headings, this is not the case! They use shortcut keys within their screen reader to access headings on the page.
Also remove it from <div tabindex=0 class="md-body"> as that is also not interactive.
Finally it is likely you do not need tabindex="-1" on the modal itself as when you open the modal you should focus either the first interactive element or the close button (which in your case appears to be the cancel button anyway.), however there may be functionality in your software that I am not aware of so that is just a point to consider.

Screen Reader not reading <h1> on entering the screen

My HTML is as below (an extracted portion):
<h1><span>Main Menu</span></h1>
<div>
<button tabindex="0">New Customer</button><br>
<button tabindex="0">Existing Customer</button>
</div>
I am using NVDA. When the user enters the screen, the focus is on the first button. NVDA reads the text on the first button(New Customer). It skips the heading (h1). I assumed it should read the h1 tags without any of the aria tags like aria-label. Am I wrong in my assumption? What do I need to do to make this work?
If you want a group label around your buttons that will be read when focus moves into the group, then use role='group' and aria-label on the container. The group label will be read when you TAB forward into the group ("New Customer") and when you TAB backwards into the group ("Existing Customer").
<div role="group" aria-label="Main Menu">
<div>
<button>New Customer</button><br>
<button tabindex="0">Existing Customer</button>
</div>
</div>
The above example does not visually show the text "Main Menu". You can still have that displayed if you want using:
<div role="group" aria-label="Main Menu">Main Menu
<div>
<button>New Customer</button><br>
<button>Existing Customer</button>
</div>
</div>
You could also use a <nav> element, but with your brief snippet, it doesn't sound like the buttons are for navigation so I wouldn't recommend this but wanted to show it for completeness:
<nav aria-label="Main Menu">
<div>
<button>New Customer</button><br>
<button>Existing Customer</button>
</div>
</nav>
If you are using a script to send focus to the first button, screenreader software will skip over anything in the markup before that and go straight to the button. You should be able to use the arrow keys to go back up to hear it.
Generally it’s best practise to let the user control focus with clicks/taps instead of automating it for them on page load.

Does one need to put aria-label on a form submit button?

As the title says, for accessibility we need to put a aria-label on elements where label text is not visible or not present, for example a dismiss button with only an "X".
I take it as if a button has text, then the aria-label won't be needed, for example:
<button>Submit</button>
But what about:
<input type="submit" value="Submit"/>
Does the input submit button need an aria-label?
This depends, both of your examples wouldn’t need it per se, because there is visible (and readable) text. If you want to describe the element in greater detail you’d use the title attribute before you use the aria-label attribute; as stated in the standard text.
<button title="Continue here after you’ve filled out all form elements">Submit</button>
<input title="Continue here after you’ve filled out all form elements" value="Submit" type="submit">
The thingy with the closing x is somewhat different, because if I say to you “x” what do you think? That’s why we want to help the screen readers (or other technology). But a title attribute would still be better and create a visible tooltip for all users.
<span aria-hidden="true">x</span>
ARIA navigation example that validates (see comments).
<!doctype html>
<html>
<head><meta charset="utf-8"><title>ARIA Example</title></head><body>
<!--
More HTML that makes up your document
-->
<!--
We apply the role navigation on the nav element to help older browsers, of course
the nav element already has the ARIA meaning, but it doesn't hurt anyone to make
sure that all browsers understand it
-->
<nav role="navigation">
<!--
Omit title from display but not from e.g. screen readers see h5bp.com/v or
h5bp.com/p
-->
<h2 class="visuallyhidden">Main Navigation</h2>
<ul role="menu">
<li>
Home
</li>
</ul>
</nav>
<!--
More HTML that makes up your document
-->