Building an Accessible Tab Panel – an ARIA example

Share on FacebookShare on LinkedInShare on Twitter

This post is part of my series in using ARIA. This post shows how to make a tab panel accessible using ARIA. You can see it working at http://accessibility.athena-ict.com/aria/examples/tabpanel2.shtml

Uses

roles: tab, tabpanel, tablist

States and properties: aria-controls, aria-hidden, aria- labelledby, aria-selected

Other: CSS selectors, JQuery

 

Look at the following tab panel:

Tabbed Panel 1Tabbedpanel2

 

 

 

 

 

 

 

 

When you select a tab, the content in the tab panel changes.

Note that the tab "features" is actually a heading for the features tab panel. Visually we can see it is selected, but programmatically/semantically all this information is often missed. We need to put into the code all the sematic information that is visually implied.

 

Step by Step: How we made the tab panel accessible

In the first blog post on ARIA, we discussed 5 steps to making complex things accessible with ARIA. Here they are again:

  1. Alert users to what each elements is: Their role (such as checkbox).
  2. Alert users to their properties and important relationships (such as disabled, required, and other labels).
  3. Alert users to what each element is doing: The state (such as checked).
  4. Alert users to changes in their state.
  5. Make sure widgets are keyboard accessible and focus works predictably. Events can be triggered though the keyboard, and it should be intuitive to the user. All controls should receive focus via tabbing though the keyboard.

Now we are going to apply these steps to our tab panel.

Step 1: Set roles

We have two tabs, and tabs need to be in a containing tab list. You can think of them as list items in a list.

<ul class="tablist" role="tablist">

<li id="tab1" role="tab" ...>Prices</li>

<li id="tab2" role="tab" ...>Features </li>

</ul>

We also have tab panels:

<div id="panel1" class="panel"  role="tabpanel" .... >

<h3 ....>Prices</h3>

List of prices</div>

Note that the panel content uses headers, as per good accessible authoring practice. Any content in the panels needs to be accessible.

Step 2: Set properties and important relationships

We want to make sure the structure, relationships between elements and groups, and properties and labels are all clear in the code. They can be clear from the standard HTML code or using ARIA.

First, note the structure. The tabs are all wrapped inside the tablist role so it is implied in the code or DOM that they belong together. That group is clear. However, the relationship between the tabs and the panel is not clear from the code, so we need to use ARIA.

With tab panels, the tabs really label the panels (they are a bit like headers) and the tabs control the panels. In other words clicking on the tabs controls what panel is visible. So now we have:

<ul class="tablist" role="tablist">

<li id="tab1" class="tab" aria-controls="panel1" role="tab" ...>Prices</li>

<li id="tab2" class="tab" aria-controls="panel2" role="tab" ..>Features </li>

</ul>

<div id="panel1" class="panel" aria-labelledby="tab1" role="tabpanel" ..>

<h3 ..>Prices</h3>

List of prices

</div>

<div id="panel2" class="panel" aria-labelledby="tab2" role="tabpanel" ..>

<h3 ..>Features</h3>

List of product features....

</div>

Note that it is always worth looking at any requirements such as required states and supported states for your roles before deciding what properties and states you need.

Step 3: Set the initial states

A good rule of thumb: elements that change how they look often have states that can change. The tabs change from looking selected to looking not selected. The panels change from being visible to non-visible. So we are going to use aria-hidden and aria-selected as our states.

We also use CSS selectors to change the look of the tabs and show and hide the panels based on the aria-hidden value. Now I do not need to change their CSS class in the code, only the value of aria-hidden or aria-selected.

In the CSS:

li[aria-selected='true'] {

color: black;

background-color: #fff;

border-bottom: 1px solid white;

}

div[aria-hidden='true'] {

display: none;

}

And the updated HTML:

<ul class="tablist" role="tablist">

<li id="tab1" aria-controls="panel1" aria-selected="true" role="tab"..>

Prices</li>

<li id="tab2" aria-controls="panel2" role="tab" aria-selected="false" ..>

Features </li>

</ul>

<div id="panel1" aria-labelledby="tab1" role="tabpanel" aria-hidden="false">

<h3 ..>Prices</h3>

List of prices

</div>

<div id="panel2" aria-labelledby="tab2" role="tabpanel" aria-hidden="true">

<h3 ..>Features</h3>

List of product features....

</div>

A word about backward compatibility....

Unfortunately, IE 9 does not support the CSS selectors above. (IE 10 does support them.) So to make this work with older browsers I added a class hidden:

.hidden {display:none;}

I then added "hidden" to the class attribute in the HTML to hide the hidden items in older browsers.

class = "... hidden"

 

Step 4: Set changes in state

We discussed above that we are using the aria states of aria-hidden and aria-selected, and we are using CSS selectors so that whenever aria-hidden='true' the panels will be hidden and when aria-hidden='false' the panels will be visible. The look of the tabs will likewise change based on the value of aria-selected.

In the code we have a function to change the values of aria-hidden and aria-selected whenever a tab is clicked. Note, this uses jQuery, but you can have the same functionality using JavaScript functions and events.

$("li[role='tab']").click(function(){

$("li[role='tab']").attr("aria-selected","false"); //deselect all the tabs

$(this).attr("aria-selected","true");  // select this tab

var tabpanid= $(this).attr("aria-controls"); //find out what tab panel this tab controls

var tabpan = $("#"+tabpanid);

$("div[role='tabpanel']").attr("aria-hidden","true"); //hide all the panels

tabpan.attr("aria-hidden","false");  // show our panel

});

For older browsers, whenever I set aria-hidden = "true" in the script, I also added the class hidden to the same selector:

$("....").addClass("hidden");

I also removed it when I set aria-hidden = "false":

$("...").removeClass("hidden");

Or, if I do not want to use jQeary, I set the class name to panel (and delete any other classes)

"...".className = "panel";

This makes the script much more robust in older browsers.

Step 5: Manage focus and keyboard accessibility

Both the focus and keyboard accessibility needs to be managed. First, let's make sure the tabs and visible tab panel can be tabbed from the keyboard. As our tabs are line items, they would not normally receive focus or be in the tab order. To change this we add tabindex = 0 to any element that should be able to receive focus. Now the completed HTML looks like:

<ul class="tablist" role="tablist">

<li id="tab1" aria-controls="panel1" aria-selected="true" role="tab" tabindex="0">

Prices</li>

<li id="tab2" aria-controls="panel2" role="tab" aria-selected="false" tabindex="0">

Features </li>

</ul>

<div id="panel1" aria-labelledby="tab1" role="tabpanel" aria-hidden="false">

<h3 tabindex="0">Prices</h3>

List of prices

</div>

<div id="panel2" aria-labelledby="tab2" role="tabpanel" aria-hidden="true">

<h3 tabindex="0">Features</h3>

List of product features....

</div>

Next let's deal with the keyboard accessibility and make sure our jQuery selectors are device independent. In the code we used the mouse event "click()" to trigger the button function: $("li[role='tab']").click(function()

We need to add events as well for people who can not use a mouse.

The following function gives keyboard accessibility for our tabs, so that pressing enter when a tab has focus (key-code 13) is the same as clicking the tab with a mouse.

$("li[role='tab']").keydown(function(ev) {

if (ev.which ==13) {

$(this).click()

}

});

For this design pattern we want the right and left arrow keys inside the tab list to switch the tabs, moving the focus and toggling the selection. Altogether the code for this keyboard functionality looks like this:

//This adds keyboard function that pressing an arrow left or arrow right from the tabs toggle the tabs.

$("li[role='tab']").keydown(function(ev) {

if ((ev.which ==39)||(ev.which ==37)) {

var selected= $(this).attr("aria-selected");

if (selected =="true"){

$("li[aria-selected='false']").attr("aria-selected","true").focus() ;

$(this).attr("aria-selected","false");

var tabpanid= $("li[aria-selected='true']").attr("aria-controls");

var tabpan = $("#"+tabpanid);

$("div[role='tabpanel']").attr("aria-hidden","true");

tabpan.attr("aria-hidden","false");

}

}

});

[hs_action id="8091, 6657"]

Leave a Reply

You can use these HTML tags:

<a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>