Photo of laptop with programming applications open

ARIA Tab Panel Accessibility: A11y Support Series

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.

  1. Each role=tab element is the tab UI control you click to activate the tab panel you want to see.
  2. All role=tab controls have to be contained within a role=tablist element so the screen reader knows how many tabs there are total.
  3. Each role=tab control needs an aria-controls attribute pointing to the ID reference of the tab panel it opens.
  4. The selected tab must have aria-selected=true and the unselected tabs need aria-selected=false.
  5. Each of the tab’s content panels must have role=tabpanel.
  6. 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.

ARIA Tab Panel Support
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.

Photo of Paul J. Adam

About Paul J. Adam

Paul J. Adam is a former Accessibility Evangelist for Deque Systems. His focus is on Mobile Accessibility and Modern Web Accessibility, with expertise in Mobile Web, Native iOS & Android, Hybrid Apps, Responsive Web Design, HTML5, JavaScript, WAI-ARIA, WCAG 2.0, and Modern Web development techniques. Paul’s been a registered Apple Developer since 2011, and spends his free time creating iOS apps and learning modern JavaScript development.
update selasa

Comments 5 responses

  1. Is this enough to meet WCAG criterion about announcing content changes to assistive tech users?

  2. Another question – I tried your example with my screenreader and I was unable to get it to read out the actual content of the tab panel. What am i doing wrong?

Leave a Reply

Your email address will not be published. Required fields are marked *