The following post is the first in Paul Adam’s new blog post series, “WAI-ARIA Widgets, Design Patterns, and Accessibility Support .” This series will outline how to build many of the different Custom Controls and Widgets defined in the WAI-ARIA 1.1 Specification from the W3C, and what screen readers they’re compatible with.*
There are many examples of different ARIA widgets on the web, but they don’t always work in all screen readers, and some don’t even work in mobile devices. The widgets we’ll build will be supported in multiple desktop and mobile screen readers. For my first post in the series, I’m going to start with how to build an ARIA Tab Panel Widget and document what ARIA attributes are supported in which screen readers. In later posts I’ll go over design patterns and how to build the widget with HTML & JavaScript. I’ll also cover testing the accessibility support levels. In order to get started, let’s have a look at ARIA in brief.
*The instructions for designing an ARIA widget, which attributes, roles, and properties to use are found in the Authoring Practices document, http://www.w3.org/TR/wai-aria-practices-1.1/, but the accessibility support with screen readers and browsers in different operating systems is not included.
Understanding ARIA
For those of you new to accessibility (or for those who just need a refresher) it’s important to understand the basics, beginning with ARIA. What is it? ARIA stands for Accessible Rich Internet Applications. It’s is a set of additional attributes you can add to HTML documents to create accessibility semantics for complex widgets like dialogs and tab panels. ARIA helps you manage the accessibility API’s Name, Role, Value, and State in custom widgets.
ARIA isn’t necessary in native HTML components like <button> and <select> which are already keyboard and screen reader operable. With ARIA you can recreate almost any native HTML element’s semantics as a custom ARIA widget constructed out of <div> tags.
What About ARIA Support?
All modern screen readers support ARIA attributes, but there are always bugs and inconsistencies in support – from browser, to screen reader, to operating system. The only way to test if ARIA is working properly is to make a live demo, and then use it with the screen reader and keyboard.
First, let’s test which major screen readers and browser combinations have properly working ARIA tab panel widgets.. We’re looking for support for the attribute/values role=tab, role=tabpanel, role=tablist, aria-controls, and aria-labelledby.
How to Build an ARIA Widget
The best reference to start building an ARIA Tab widget is the WAI-ARIA Authoring Practices 1.1 for Tab Panel.
- Each role=tab element is the tab UI control you click to activate the tab panel you want to see.
- All role=tab controls have to be contained within a role=tablist element so the screen reader knows how many tabs there are total.
- Each role=tab control needs an aria-controls attribute pointing to the ID reference of the tab panel it opens.
- The selected tab must have aria-selected=true and the unselected tabs need aria-selected=false.
- Each of the tab’s content panels must have role=tabpanel.
- If you set aria-labelledby on the tabpanel pointing to the ID of the tab control, you’ll give each tabpanel a unique accessible name.
Your JavaScript must dynamically change the aria-selected=true/false state on each role=tab. It also has to hide and show each role=tabpanel, using CSS display:none to hide panels and display:block to show panels. Visibility:hidden works as well.
Below is the HTML code to create the structure for our tab panel and the CSS to control the layout and style. Notice we use CSS [attribute] selectors to style our tab panel rather than class names.
HTML/CSS Code
<h2 style="text-align:center">Pick your favorite fruits!</h2> <div id="tabbed-interface"> <style> [role=tabpanel] {border-top:1px solid black; padding: .5em 0;} [role=tablist] { padding: .2em 0;} #tabbed-interface {border:1px solid black; text-align:center; margin:0 10%;} </style> <div role="tablist" aria-orientation="horizontal"> <button role="tab" aria-selected="true" id="apples-tab" aria-controls="apples-content-panel" style="font-weight:bold">Apples</button> <button role="tab" aria-selected="false" id="bananas-tab" aria-controls="bananas-content-panel">Bananas</button> <button role="tab" aria-selected="false" id="peaches-tab" aria-controls="peaches-content-panel">Peaches</button> </div> <div id="apples-content-panel" role="tabpanel" aria-labelledby="apples-tab"> <h2>Apples</h2> <p>Apples are amazing!</p> </div> <div id="bananas-content-panel" role="tabpanel" aria-labelledby="bananas-tab" style="display:none"> <h2>Bananas</h2> <p>Don't slip on your banana peel! </p> </div> <div id="peaches-content-panel" role="tabpanel" aria-labelledby="peaches-tab" style="display:none"> <h2>Peaches</h2> <p>Peaches keep peaching! </p> </div> </div>
Keyboard Behavior
The ARIA Authoring Practices recommends that only one tab control is in the focus order at a time then tabbing. It also outlines how to open other tabs with the keyboard: press the right and left arrow keys to cycle through and open each tab. That means the user would TAB only once into the tablist. TABbing again would allow the user to exit the tablist.
There’s a problem with this behavior, though. Most sighted keyboard users and screen reader users probably expect all tabs to be TABable. A mouse user can directly access the tab they desire with a single click without needing to click on each of the previous tabs. That means that every time a keyboard user uses the recommended ARIA tab panel pattern, they’re forced to press the arrow key and open every single tab until they reach the desired tab.
By now you’ve probably come to the same realization that I did – this is very confusing and creates unnecessary work for the user. I think that only the desired tabs should be opened by the user, which is why I’ve decided to go with a simple “all tabs are in the tab focus order” modification to the ARIA pattern.
As an added bonus, it’s also much less code avoiding arrow key navigation and tabindex=-1 modification. And another added benefit of using less code is that my role=tab elements are constructed out of native HTML <button> tags. These are, by default, keyboard focusable and operable with the spacebar and enter keys when a click event is placed on the <button>. If your tabs were made of <a href> then you’d have to add an extra spacebar JavaScript event key handler. If you make them out of <div> tags then you must add a tabindex=0, a focus outline, and both enter and spacebar JS events.
JavaScript Code with //comments
<script> var tabs = document.querySelectorAll('[role=tab]'); //get all role=tab elements as a variable for (i = 0; i < tabs.length; i++) { tabs[i].addEventListener("click", showTabPanel); } //add click event to each tab to run the showTabPanel function function showTabPanel(el) { //runs when tab is clicked var tabs2 = document.querySelectorAll('[role=tab]'); //get tabs again as a different variable for (i = 0; i < tabs2.length; i++) { tabs2[i].setAttribute('aria-selected', 'false'); tabs2[i].setAttribute('style', 'font-weight:normal'); } //reset all tabs to aria-selected=false and normal font weight el.target.setAttribute('aria-selected', 'true'); //set aria-selected=true for clicked tab el.target.setAttribute('style', 'font-weight:bold'); //make clicked tab have bold font var tabPanelToOpen = el.target.getAttribute('aria-controls'); //get the aria-controls value of the tab that was clicked var tabPanels = document.querySelectorAll('[role=tabpanel]'); //get all tabpanels as a variable for (i = 0; i < tabPanels.length; i++) { tabPanels[i].style.display = "none"; } //hide all tabpanels document.getElementById(tabPanelToOpen).style.display = "block"; //show tabpanel who's tab was clicked } $('[role=tablist]').keydown(function(e) { if (e.keyCode == 37) { $("[aria-selected=true]").prev().click().focus(); e.preventDefault(); } if (e.keyCode == 38) { $("[aria-selected=true]").prev().click().focus(); e.preventDefault(); } if (e.keyCode == 39) { $("[aria-selected=true]").next().click().focus(); e.preventDefault(); } if (e.keyCode == 40) { $("[aria-selected=true]").next().click().focus(); e.preventDefault(); } }); </script>
Screen Reader Support Test Results
Below is a table that lists each ARIA Tab Panel attribute and what screen reader/browser combination supports that attribute.
Attribute/Value/IDref | OS X 10.11/Safari | OS X 10.11/Chrome | VoiceOver iOS 9.3/Mobile Safari | TalkBack Android/Chrome | TalkBack Android/Firefox |
---|---|---|---|---|---|
role=tab | Yes | Yes | Yes | Yes | Yes |
role=tablist | Yes | Yes | Yes | Yes | Yes |
role=tabpanel + aria-labelledby | Yes | Yes | Yes | Yes | No |
aria-selected | Yes | Yes | Yes | Yes | Yes |
ARIA Tab Panels have Great Support
I think you can code the keyboard interaction in ways that are not exactly as recommended in the ARIA Authoring Practices. Bug reports should be filed for the user agents and assistive technology combinations where ARIA tab attributes are not working properly. Stay tuned for my next post in the accessibility support series, which will focus on role=alertdialog modal alert dialog widgets.