Yahoo! Developer Network Blog
« Previous | Main | Next »
October 12, 2009
Accessible Tabs in the new Yahoo Homepage - recreated with YUI3 and WAI-ARIA
One of the things you might not be aware of when it comes to Yahoo's new shiny front page is that a lot of accessibility work went into it.
As always this is nothing that comes from high up but is the work of dedicated developers on the ground. one of these developers is Ian Pouncey and we are very happy to have him here as a guest blogger to tell us about the tricks he used to make the tabs inside the new Yahoo homepage available to all users - regardless of technical set up or physical ability. Take it away, Ian.
WAI-ARIA enabled tabs with YUI3
This is a follow on from the feature I wrote for the issue 195 of .net magazine as part of an article on the new Yahoo! front page. Don't worry if you don't have this magazine, I'll cover everything you need here.
I was intending to provide you with exactly the code you need, however some clever folks on the YUI team have beaten me to it! so instead I will be adding my own thoughts to their Accessible TabView implementation.
First of all you shouldn't try to write this kind of interactive functionality without a good framework. In this case our framework is the shiny and new YUI3, but if YUI is not your cup of tea I can highly recommend Dirk Ginader's blog post on jQuery Accessible Tabs.
Before we dive in to the code go and take a look at the example implementation. Try using it with only a keyboard - you will find that once one of the tabs has focus you can navigate to the other tabs using the left and right arrow keys and activate the selected tab by pressing return. This exactly mimics the interaction with a mouse or other pointer device and is the way tabs in operating systems work.
Six steps
The article in the .net magazine breaks down the creation of accessible tabs into 6 steps. Here's how they relate to the YUI code.
Step 1: Content and structure
Tabs are represented by a list of<a>elements, each one a 'tab', whose href attribute is set to the id of a<div>'tab panel' which contains the content for that tab. Without JavaScript the tabs will function as in-page links. Add CSS for basic styling.
For the YUI version the code looks like:
<div id="tabview-1">
<ul>
<li class="yui-tab yui-tab-selected"><a href="#top-stories"><em>Top Stories</em></a></li>
<li class="yui-tab"><a href="#world-news"><em>World</em></a></li>
<li class="yui-tab"><a href="#entertainment-news"><em>Entertainment</em></a></li>
<li class="yui-tab"><a href="#sports-news"><em>Sports</em></a></li>
<li class="yui-tab"><a href="#technology-news"><em>Technology</em></a></li>
</ul>
<div>
<div class="yui-tabpanel yui-tabpanel-selected" id="top-stories">
<!-- Tab Panel Content Here -->
</div>
<div class="yui-tabpanel" id="world-news">
<!-- Tab Panel Content Here -->
</div>
<div class="yui-tabpanel" id="entertainment-news">
<!-- Tab Panel Content Here -->
</div>
<div class="yui-tabpanel" id="sports-news">
<!-- Tab Panel Content Here -->
</div>
<div class="yui-tabpanel" id="technology-news">
<!-- Tab Panel Content Here -->
</div>
</div>
</div>
Quite straight forward, so let's move on.
Step 2: Adding JavaScript
Using JavaScript add a class of 'js' to the tabs container and a class of 'enabled' to the initially selected tab and tab panel. Use the 'js' class to hook styles to highlight the enabled tab and hide all but the enabled tab panel.
YUI does things slightly differently. First it dynamically loads any JavaScript and CSS assets it needs when you instantiate the code, and secondly it uses a specific class which applies only to the tab view, rather than a generic 'js' class.
tabView.addClass("yui-tabview");
It also uses a classes of 'yui-tab-selected' and 'yui-tabpanel-selected' rather than 'enabled'. Other than that, the logic is the same. The YUI solution simply provides you with more hooks to style and access the tabs.
Step 3. Add Javascript interaction
With JavaScript remove the 'enabled' class from the current tab and tab panel when a user activates the link with either keyboard or mouse, and add it to the selected tab and tab panel. Settabindexof the tab panel to '0' and callfocus()on it.
The code to do this in YUI is:
if (selectedTabAnchor) {
// remove classes from tab and tab panel
selectedTabAnchor.get("parentNode").removeClass("yui-tab-selected");
Y.one(("#" + panelMap[selectedTabAnchor.get("id")])).removeClass("yui-tabpanel-selected");
}
// assign reference to the tab that has been activated to selectedTabAnchor
selectedTabAnchor = this;
// add class to activated tab
selectedTabAnchor.get("parentNode").addClass("yui-tab-selected");
// get the tab panel based on the active tab (cached in panelMap)
selectedPanel = Y.one(("#" + panelMap[sID]));
// add class to activated tab panel
selectedPanel.addClass("yui-tabpanel-selected");
The YUI version doesn't automatically set focus when you activate a tab, but it has a few other tricks up its sleeve as we will see later. Setting and removing the tabindex of an element makes our tabs keyboard accessible to all JavaScript supporting browsers. With newer browsers however we can tap into the richness of ARIA to build accessible interfaces
Step 4. ARIA support and Step 5. More ARIA
Steps 4 and 5 are combined in this implementation.
Using JavaScript add a role attribute with a value of 'tablist' to the list containing the tabs, a role of 'presentation' to each of its list elements, a role of 'tab' to each of the tabs and a role of 'tabpanel' to each of the tab panels.
Again using JavaScript add an 'aria-labelledby' attribute to each of the tab panels. The value for each one should be theidattribute value of the associated tabs parent<li>element. Next add an 'aria-selected' attribute with value of 'true' to the currently enabled tab.
This is easy with YUI:
// loop through each tab
tabView.all(".yui-tab > a").each(function (anchor) {
// set role attributes on tab
anchor.set("role", "tab");
anchor.get("parentNode").set("role", "presentation");
// get panel based on the tab href attribute value
// (which is stored as sPanelID)
panel = Y.one(("#" + sPanelID));
// set role and aria-labelledby attributes on tab panel
panel.setAttrs({
role: "tabpanel",
"aria-labelledby": anchor.get("id")
});
});
The aria-selected attribute is not set at all. The nice thing about the YUI team is that they are always happy to receive suggestions for improvements, and will incorporate good ideas in to the code. Maybe in the future this will be added.
Step 6. ARIA interaction JavaScript
Write support for navigation of tabs using left and right arrow keys. Set the tabindex of the enabled tab to '0' and the other tabs to '-1'. The user will then be able to navigate the tabs as a single control instead of a list of links.
Keyboard navigation like this is something that can be tricky to write cross browser without a framework. I'm not going to try and explain the magic of the Y.Plugin.NodeFocusManager - it will make my brain hurt! The important thing to see is that the code is nice and compact. Really, if you are doing this without a framework you are doing it wrong.
tabView.plug(Y.Plugin.NodeFocusManager, {
descendants: ".yui-tab > a",
keys: { next: "down:39", // Right arrow
previous: "down:37" }, // Left arrow
focusClass: {
className: "yui-tab-focus",
fn: function (node) {
return node.get("parentNode");
}
},
circular: true
});
Further enhancements
In addition to the ARIA enhanced interactions in the six steps TabView gives you extra 'Accessibility Sugar'.
One of these sweeteners is the addition of instructional text for screen reader users:
tabHeading.set("innerHTML", (sInstructionalText + " <em>Press the enter key to load the content of each tab.</em>"));
Where possible you should show such text to all users - screen readers users are not the only ones who need to use a keyboard - but if you can't fit it in to your design this is better than nothing.
It also adds an additional mechanism for navigating the tab content in the form of previous and next buttons:
if (listitem.previous()) {
sHTML += '<button type="button" class="yui-tabview-prevbtn">Previous Tab Panel</button>';
}
if (listitem.next()) {
sHTML += '<button type="button" class="yui-tabview-nextbtn">Next Tab Panel</button>';
}
In conclusion I have to say that I am very impressed with what the YUI team have come up with. There may be a few things missing, but you could add these yourself with far less effort than would be required to write this from scratch.
Accessibility for complex interactions can be complicated, but with the right tools it becomes a whole lot easier.
Ian Pouncey
Web Developer, Yahoo! London
Posted at October 12, 2009 12:32 PM | Permalink
Comments
Excellent Article Ian!
Thanks a lot :-)
Posted by: Dirk Ginader at October 12, 2009 1:40 PM
Post a comment
Comment Policy: We encourage comments and look forward to hearing from you. Please note that Yahoo! may, in our sole discretion, remove comments if they are off topic, inappropriate, or otherwise violate our Terms of Service. Fields marked with asterisk '*' are required.
Subscribe
Recent Blog Articles
view all
YQL Open Table for Google Buzz now live
Tue, 09 Feb 2010
INSERT INTO twitter.status ...
Mon, 08 Feb 2010
Announcing the Yahoo! Brasil Open Hack Day 2010, 20-21 March
Mon, 08 Feb 2010
Marketing hacks, linchpins, and tech women of valor
Sun, 07 Feb 2010
Yahoo! India invites you to join the first India Hadoop Summit
Thu, 04 Feb 2010
Recent Links
Appcelerator Titanium + Yahoo YQL on Vimeo
Mon, 08 Feb 2010
Tue, 02 Feb 2010
PhoneGap | Cross platform mobile framework
Sat, 30 Jan 2010
Web developers can rule the iPad - O'Reilly Radar
Sat, 30 Jan 2010
rc3.org - Is the iPad the harbinger of doom for personal computing?
Thu, 28 Jan 2010
Archives
2010
2009
2008
2007
2006
2005
Recent Readers

