Tuesday, February 12, 2013

ARIA Tabs

Photo of whiteboard and ARIA tabs sketch.

Last week I spent my Friday afternoon trying to get my head around how to apply ARIA properly to a tabbed interface. I even got so far as to map it out on my whiteboard and snap a photo so I could mull it over during the weekend.

And then the very next day Marco Zehe, responsible for accessibility quality assurance at Firefox, posted Advanced ARIA tip #1: Tabs in web apps and suddenly my weekend of snow shoveling turned into fiddling.

Marco's post included sample HTML for tabs and an outline of how the script to control it should function, but did not include the necessary styles or script to make it behave as tabs. Since I was marking up a tab list anyway to incorporate ARIA, I'm sharing my code here for others to try, enhance, and so on. It's also on CodePen, so you can fork it and fiddle there.

The HTML

My code has minor differences from the example. For instance, I add a return false; at the end of the event handler. I also call the function that activates the first tab at the bottom of the page, so all tabs start as un-selected and no tab panels are visible until that function fires. You can just as easily put the logic into your HTML and CSS to have one pre-selected and skip that function call altogether.

<ul class="tabList" id="tabs" role="tablist">
  <li role="presentation"><a id="tab1" href="#panel1" onclick="showTab(1);return false;" role="tab" aria-controls="panel1" aria-selected="false">Tab 1</a></li>
  <li role="presentation"><a id="tab2" href="#panel2" onclick="showTab(2);return false;" role="tab" aria-controls="panel2" aria-selected="false">Tab 2</a></li>
  <li role="presentation"><a id="tab3" href="#panel3" onclick="showTab(3);return false;" role="tab" aria-controls="panel3" aria-selected="false">Tab 3</a></li>
</ul>

<div class="tabPanels">
  <div id="panel1" role="tabpanel" aria-labelledby="tab1">
  <p>
    Nulla tincidunt pharetra tortor. In dapibus ultricies arcu. Suspendisse at purus eu est tincidunt feugiat. Praesent et sapien. Vivamus fermentum, diam vel ornare vestibulum, nibh massa imperdiet lectus, eget tincidunt urna urna nec erat. Curabitur interdum. Nam lorem nunc, posuere quis, suscipit eu, hendrerit vitae, nisi. Etiam hendrerit tincidunt felis.
  </p>
  </div>

  <div id="panel2" role="tabpanel" aria-labelledby="tab2">
  <p>
    Vestibulum id eros eu lorem tincidunt sollicitudin. Suspendisse ligula. Sed nisi magna, elementum at, ultricies in, tincidunt imperdiet, quam. Nulla semper. Suspendisse potenti. Sed sollicitudin dolor aliquet purus. Aliquam dui. Proin arcu metus, porttitor eget, pulvinar nec, molestie dapibus, ligula.
  </p>
  </div>

  <div id="panel3" role="tabpanel" aria-labelledby="tab3">
    <p>
      Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam vel erat. Vestibulum egestas purus ut felis. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
    </p>
  </div>
</div>

<script>
  showTab(1);
</script>

The CSS

This CSS presumes you've already set your typefaces, your page background, and everything works as you want. I have put the minimum styles to make it visually look like tabs. You may notice that I use two different selectors for both a selected tab and for a hidden tab panel. One is by a class name, the other is by the value of the appropriate aria- attribute. Use the first for broader (older) browser support and the latter if you don't care. If you do target just current browsers, then you may adjust the script below to skip writing classes.

.tabList {
  list-style-type: none;
  padding: 0;
  margin: 0 auto;
}

.tabList a {
  display: block;
  float: left;
  border: .1em solid #000;
  padding: .25em 2em;
  margin: 0 0 -1px .25em;
  border-radius: .5em .5em 0 0;
  background-color: #aaa;
}

.tabList a:link, .tabList a:visited, .tabList a:hover, .tabList a:focus, .tabList a:active {
  text-decoration: none;
  color: #000;
}

.tabList a:hover, .tabList a:focus {
  background-color: #ccc;
}

.tabList a.selected {
  background-color: #fff;
  border-bottom: 1px solid #fff;
}

.tabPanels div {
  clear: left;
  margin: 0 auto;
  padding: 1em 2em;
  border: 1px solid #000;
  border-radius: .25em;
  background-color: #fff;
  display: none;
}

.tabPanels div.selected, div[aria-hidden=false] {
  display: block;
}

.hide, div[aria-hidden=true] {
  display: none;
}

The Script

The following script toggles classes for the tabs and the tab panels, as well as adjusting the aria-selected and aria-hidden attributes. There is no keyboard functionality in it at all, but I am always willing to take some from a kind donor. As I noted above, if you use solely the aria- attribute as a CSS selector, you can drop the part that changes the class for each element.

var OpenTab;

function showTab(num) {
  try{
      if(OpenTab!=undefined){
        var OldTabID = document.getElementById('tab'+OpenTab);
        var OldPanelID = document.getElementById('panel'+OpenTab);
        OldTabID.className = '';
        OldPanelID.className = 'hide';
        OldTabID.setAttribute('aria-selected', false);
        OldPanelID.setAttribute('aria-hidden', true);
      }
      var TabID = document.getElementById('tab'+num);
      var PanelID = document.getElementById('panel'+num);
      TabID.className = 'selected';
      PanelID.className = 'selected';
      TabID.setAttribute('aria-selected', true);
      PanelID.setAttribute('aria-hidden', false);
      OpenTab = num;
  }catch(e){}
}

An Example

The following is an embedded version of the tabs on CodePen. Because of how CodePen works, you'll see a few minor differences in styles, but this is at least a functional example which you can fork and tweak.

For example, on CodePen, the function to enable the first tab must be called in the block of script itself, but I prefer to call it at the bottom of the page.

For reasons I cannot figure out, the tabs on the embedded version of this Pen do not work. Visit the tabs directly on CodePen to see them in action.

Check out this Pen!

Wrap-up

That's it, pretty simple. If you have suggestions, corrections, or are a regular AT user and can offer further insight, please feel free to share in the comments or tweet me on the Twitters.

Update, August 6, 2013

Marco Zehe, the guy who wrote the article Advanced ARIA tip #1: Tabs in web apps (which I link above) offered some adjustments to make to my sample code. In essence, dump the aria-hidden from the HTML and use the CSS style visibility: hidden; in its place. His explanation:

Update, December 4, 2013

Heydon Pickering has an example of ARIA tabs as well, with slightly different approach (aria-controls instead of aria-labelledby). He also offers a tip for maximizing compatibility:

8 comments:

  1. Thanks for sharing I am currently tackling a slightly similar project along these lines myself, and I couldn't help but notice that you seem to have added aria-hidden, Marcos example didn't use this, what were your reasons for adding this attribute?

    ReplyDelete
    Replies
    1. To qualify my answer, I could be wrong. I am hoping some AT users chime in here.

      I opted to add the aria-hidden because sometimes a developer may not want to use display: none to hide a tab. If the dev wants an animated CSS effect, then that dev may use height, width, opacity, etc. I do that very thing on the Algonquin Studios home page (I use min-height), so adding aria-hidden helps make the element go away for AT users.

      Steve Faulkner explains the differences between the hidden attribute, aria-hidden and display:none in his post HTML5 Accessibility Chops: hidden and aria-hidden.

      Delete
    2. Thanks for the clarification, I'll check out the post you have linked to.

      Delete
    3. And then I re-found this today: How-to: Hide Content. It explains that display:none can sometimes pose problems for screen readers.

      Delete
    4. I may have been wrong about aria-hidden. Marco offered feedback via The Twitters. Check out his comments embedded at the end of the post.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. Actually, he's changed his mind about that to: http://www.marcozehe.de/2013/08/02/how-i-came-to-grudgingly-accept-aria-hidden/

    aria-hidden isn't universally supported by screen readers, but I don't think it would do any harm.
    The W3C suggest using some CSS such as:
    [aria-hidden="true"] { visibility: hidden; }

    From:
    http://www.w3.org/TR/wai-aria/states_and_properties#aria-hidden

    Although I'm not sure that's going to be very robust yet.

    ReplyDelete
    Replies
    1. Marco's tweets are a reply to me referencing his article in a tweet (Inception has nothing on how we all interact).

      Since I've already got a selector on div[aria-hidden=true] to set display:none, as you note it's just a matter of adding the visibility style and being done with it.

      I just need to test it.

      Delete