First steps with WAI-ARIA

On the line, originally uploaded by eat your greens.

The National Maritime Museum launched a podcast this summer. It’s called On the line and publishes monthly talks relating to the museum, the Royal Observatory and the Queen’s House. Transcripts for each talk are provided by Casting Words – see Bruce Lawson’s blog for discussion of how great they are. I did the front-end development for the site, building it as a blog using Movable Type Open Source with the podcasting plugin. I also used WAI-ARIA in my markup for the first time. If you aren’t familiar with ARIA, I’d recommend taking a moment to follow the previous link and read Gez Lemon’s excellent introduction to the subject. ARIA is currently in ‘Working Draft’ status, but Bruce Lawson points out that it’s already supported in browsers and the W3C are encouraging authors to use it.

Why use ARIA

ARIA addresses a problem I posted about a while ago – HTML isn’t expressive enough to describe the interfaces we use on web pages. For example, the On the line pages make use of tabbed interfaces where the current tab is selected by clicking on a link in a list of links. The links are styled, visually, to look like tabs but how does a browser, or assistive technology, recognise that these links are being used as tab controls? With ARIA, we add attributes to the HTML tags which describe their role and state.


The tabbed interfaces are generated using Patrick Fitzgerald’s excellent tabber.js script. His site gives full instructions for using the script, so I’ll just give a quick overview here. We start with well-structured HTML, broken into headed sections. The overall tabbed area is given a class of tabber and each panel has a class of tabbertab. tabber.js will then convert this section of code into a tabbed interface, and generate a list of links based on the individual section headings. These links serve as the tab controls. Here’s example HTML markup.

	<div class="tabber">

	  <div class="tabbertab">
	    <h3>Section One</h3>
	    Section one content.

	  <div class="tabbertab" >
		<h3>Section Two</h3>
	    Section two content.


Adding ARIA

Updating tabber.js to add ARIA support is straightforward. What we are going to do is add some new attributes to our HTML elements, to describe the role and state of those elements. We are also going to make sure that the values of the state attributes change when the state of the tabbed panels changes. You can download my modified version of the script here – tabber-aria.js. Instructions for using it are exactly the same as the original tabber.js. In this post, though, I’ll go through the changes that I’ve made to the code in order to add ARIA support.

Firstly, we have to override one of the default settings in tabber.js. We’ll want to add ids to our tabs, so that we can relate a panel to its controlling link. The addLinkId setting controls whether the tab links have ids, so we set this to true.

	/* If you want to add an id to each link set this to true */
	  this.addLinkId = true;

Next we’ll add roles to the tab control and the tab panels. We do this by editting the section of code which creates the list and adding a role attribute using the DOM’s setAttribute method. The list of controls has a role of tablist and each tab panel has a role of tabpanel

	/* Create a new UL list to hold the tab headings */
  DOM_ul = document.createElement("ul");
  DOM_ul.className = this.classNav;
  DOM_ul.setAttribute('role', 'tablist');

  /* Loop through each tab we found */
  for (i=0; i < this.tabs.length; i++) {
    t = this.tabs[i];
	t.div.setAttribute('role', 'tabpanel');

Next we modify the section of code that generates the links which act as the tabs themselves. We give each of these a role of tab and also add an aria-selected attribute to describe the current state of the tab. Initially, these will all be false. Later, we’ll update so that the active tab has aria-selected="true".

	/* Create a link to activate the tab */
    DOM_a = document.createElement("a");
    DOM_a.href = "javascript:void(null);";
    DOM_a.title = t.headingText;
    DOM_a.onclick = this.navClick;
	DOM_a.setAttribute('role', 'tab');
	DOM_a.setAttribute('aria-selected', 'false');

Next, we’ll link up the controls to their respective tab panels by using ids. First we need to make sure that the tab container, div.tabber, has an id. Check if one exists. If not, generate ids until we find one that isn’t in use, then apply the generated id to the tabber and update the DOM. I chose to use ids of the form tab0, tab1, tab2, etc.

	if (! {
		  var idRoot = 'tab';
		  var j = 0;
		  while (document.getElementById(idRoot+j)) {
				} = idRoot+j; =;

This id is then used as the root of a distinct id for each individual tab link. Once those ids have been set, each tab panel (t.div in the code) can be linked to its respective control using the aria-labelledby attribute: = aId;
	t.div.setAttribute('aria-labelledby', aId);

Now, we’ll want to update the ARIA state attributes whenever the state of the tabs changes. There are four methods which handle state changes; tabShow and tabHide handle showing and hiding the tab panels by changing the class names on each panel. I’ve modified them so that, additionally, tabShow sets aria-hidden="false" on the selected panel and tabHide sets aria-hidden="true" on each panel. Finally, the methods navSetActive and navClearActive set the currently active tab in the list of tabs. I’ve updated these to also set aria-selected="true" on the active link in navSetActive and aria-selected="false" on each of the other links in navClearActive. Here’s the modified navSetActive method as an example.

	/* Set classNavActive for the navigation list item */
	  this.tabs[tabberIndex].li.className += ' ' + this.classNavActive;
	  var a = this.tabs[tabberIndex].li.firstChild;
	  a.setAttribute('aria-selected', 'true');
	  return this;

You can download the full modified code, tabber-aria.js, and use it yourself in exactly the same way as the original tabber.js. Hopefully, the code I’ve added here shows how easy it is to use the ARIA attributes. You can see the final script in action at On the line and also on the Maritime Museum home page.