From HTML to ARIA Tabs, A Travelog

Tabs – a 3-step journey from HTML to ARIA

This article is an accompanyment to Danger! Testing Accessibility with real people

Here we take you through the steps of constructing the examples used in that article, paying particular attention to how we transform the functional html tabs with no screen reader semantics to a full-fledged tab implementation. We will make scenic stops along the way where we explain the importance of what we are adding. Photo opportunities are optional.

The basic Tabs without ARIA example is an online version of the halfway point of this journey, (after step 2), whilst tabs with ARIA example is the destination.

For a better understanding of ARIA, what it is and how to use it, we recommend giving The Accessibility Tree: A Training Guide for Advanced Web Development a spin. Then consult the ARIA authoring practices guide for advice on how to implement specific widgets, which has a very good entry for tabs.

Our goal is to make tabs on the web behave like tabs on the desktop. We’ll use some good old fashioned HTML as the basis, then enhance it as we go. In this exercise we create a tabbed form where a user provides contact information )home, work, or other). We don’t want to overload the user with unnecessary form fields, so we let them first choose the type of contact information they wish to provide so we can present only the form fields relevant to that information.

Step 1: The basic HTML

Here is the basic HTML code for our tabs example. (the CSS code is left out but is available in either of the online examples.

Note, due to formatting differences, section elements are used instead of div elements as seen within the live examples. Please use the live examples by saving them locally to run the code, since this will prevent any special character markup from clogging the works.


<h2>My preferred contact information</h2>
<ul id="tabs">
        <li><a id="homeTab" href="#home" class="tab selected">Home</a></li>
        <li><a id="workTab" href="#work" class="tab">Work</a></li>
        <li><a id="otherTab" href="#other" class="tab">Other</a></li>

<section id="home" class="tabcontent">
        <h3>Home contact information</h3>
        <p>Lots of input fields that do not matter for the purposes of demonstrating tabs.</p>

<section id="work" class="tabcontent hidden">
        <h3>Work contact information</h3>
        <p>Lots of input fields that do not matter for the purposes of demonstrating tabs.</p>

<section id="other" class="tabcontent hidden">
        <h3>Other Contact Information</h3>
        <p>Lots of input fields that do not matter for the purposes of demonstrating tabs.</p>


Note how we use CSS

  • To make links visually look like tabs.
  • To visually indicate which tab is selected
  • To display tabpanel content
  • To hide inactive tabs.

The only CSS understood by assistive technologies is the hidden class.

Step 2: Add the desired keyboard functionality.

To all intents and purposes, ARIA is a screen reader only technology (though some speech recognition tools have limited support). People rely on the keyboard for many different reasons though, so getting the keyboard interaction in place is a fundamental next step.

Like tabs on the desktop, it should be possible to move focus onto the currently selected tab, cycle through the available tabs using the arrow keys, select one, then move focus from the selected tab to the panel of content that is currently displayed.

We use the tabindex attribute to handle keyboard focus. The currently selected tab has a tabindex of 0, making it focusable. The rest of the tabs have tabindex -1. If the html base element for a tab is natively focusable (such as a link( we don’t need to set tabindex=”0″ on the currently selected tab.

When the arrow key is pressed, we:

  • Make the newly selected tab focusable (adding tabindex of 0 or removing tabindex=”-1″ if the element is natively focusable.
  • Take the old tab out of focus order (by adding tabindex of -1).

This method is known as roving tab index and ensures that only the active tab is in the focus order.

Important notes on tab selection.

According to the ARIA 1.1 authoring practices guide, (that we’re working on at W3C), a tab may be selected when an arrow key moves focus to it or when the space and enter keys are pressed with focus on the tab. The difference depends on the interface and the way the content is rendered.

In either case, to ensure accessibility across devices and with various assistive technologies, onclick must be used as the trigger to select a tab, and not onfocus or any other key event. For example when an arrow key is used to select a tab, it should trigger the onclick handler for the tab that is to be selected. This ensures that a tab cannot be opened accidentally on touch screen devices, where onfocus is triggered automatically, and prevents the tabs from becoming unusable because a key press is required on a device that has no keyboard.

Moving focus to the start of a tabpanel as soon as a tab is selected is not something everyone will find helpful, especially if you decide not to implement the keyboard handling we mention above. What if someone selects the wrong tab, or decides the tab wasn’t the one they wanted? You’ve made an assumption about what that person wanted to do, and with rare exceptions we’re not fond of people making those sort of assumptions on our behalf.

You can view the JQuery code that handles the keyboard interaction on the “Tabs with ARIA” example page linked to above.

Yes, but what about the ARIA?

Once the basic interaction has been tested with mouse, keyboard and touch, it’s time to add some ARIA to enhance the experience for screen reader users.

Step 3: Add required ARIA roles.

Now let us add the required ARIA roles to our content.

  1. Add role=”tablist” to the <ul> container that holds all the tabs. This tells assistive technologies that a set of tabs is coming up and allows them to respond in the way most beneficial to the user. Screen readers, for instance can switch from browse mode into interactive or forms mode, which passes the keyboard events directly to the webpage instead of intercepting them.
  2. Add role=”tab” to each of the <a> elements that represent those tabs. This is what tells the screen reader to forget the HTML consists of a list of links, and instead tell the user it’s a set of tabs. Make sure to always add role=”tab” on the element that has the onclick Javascript event.
  3. Add role=”presentation” to each of the <li> elements. We need to use the <li> elements so the underlying HTML structure is usable on older devices (that don’t support ARIA), but the presentation role is what stops screen readers that do support ARIA from announcing the list items and enables them to calculate and announce the number of tabs in the tablist along with the relative position of the selected tab (e.g. “tab 2 of 3” for the work tab in our example.
  4. Add role=”tabpanel” to the <section> containers. This enables the screen reader to announce the beginning and the end of the visible tabpanel content.


<h2>My preferred contact information</h2>
<ul id="tabs" role=”tablist”>
        <li role=”presentation”><a id="homeTab" href="#home" class="tab selected" role=”tab”>Home</a></li>
        <li role=”presentation”><a id="workTab" href="#work" class="tab" tabindex="-1" role=”tab”>Work</a></li>
        <li role=”presentation”><a id="otherTab" href="#other" class="tab" tabindex="-1" role=”tab”>Other</a></li>

<section id="home" class="tabcontent" role=”tabpanel”>
        <h3>Home contact information</h3>
        <p>Lots of input fields that do not matter for the purposes of demonstrating tabs.</p>

<section id="work" class="tabcontent hidden" role=”tabpanel”>
        <h3>Work contact information</h3>
        <p>Lots of input fields that do not matter for the purposes of demonstrating tabs.</p>

<section id="other" class="tabcontent hidden" role=”tabpanel”>
        <h3>Other Contact Information</h3>
        <p>Lots of input fields that do not matter for the purposes of demonstrating tabs.</p>


Step 4 (final step): Adding the ARIA attributes.

Finally, let’s add theARIA attributes that provide important context info for screen readers:

  1. Add the aria-controls attribute to each tab element. Its value is the id of the tabpanel whose display state is controlled by the tab. This attribute provides a programmatic context between the tab and its associated tabpanel. This programmatic context is important, and has already been turned into a keyboard shortcut (JawsKey-alt-m) using Jaws and Firefox. We expect more user agents to implement similar functionality.
  2. Add the attribute aria-labelledby to each tabpanel element. Its value is the ID of the tab that controls it. This is the reverse of the relationship explained in the above step and could also be used by assistive technologies, e.g. to provide a keyboard shortcut from the tabpanel to its associated tab.
  3. Add aria-selected=”true” to the currently selected tab and aria-selected=”false” to tabs that are not selected. Note how we used a CSS class to do the same visually? This is important, because screen reader user can see which tab is selected without switching to forms mode. Most screen readers automatically switch to forms or application mode when screen reader focus hits a tablist (to pass the keyboard keys directly to the page) , but advanced screen reader users often choose to override this behaviour.
  4. (optional) add aria-orientation to the tablist to indicate the direction (vertical or horizontal) of the visual tab layout. This can also indicate which set of arrow keys (up down, or left/right) to use for selecting tabs. Keep in mind that if the associated tabpanel has a lot of content, sighted keyboard only users may want to use the arrow keys up and down to scroll the page, so a horizontal set of tabs with right/left arrow keys might provide a better experience for that user group.


<h2>My preferred contact information</h2>
<ul role="tablist" id="tabs">
<li role="presentation"><a role="tab" aria-selected="true" aria-controls="home" id="homeTab" href="#home" class="tab selected">Home</a></li>
<li role="presentation"><a role="tab" aria-selected="false" aria-controls="work" id="workTab" href="#work" class="tab" tabindex="-1">Work</a></li>
<li role="presentation"><a role="tab" aria-selected="false" aria-controls="other" id="otherTab" href="#other" class="tab" tabindex="-1">Other</a></li>

<section role="tabpanel" aria-labelledby="homeTab" id="home" class="tabcontent">
<h3>Home contact information</h3>
<p>Lots of input fields that do not matter for the purposes of demonstrating tabs.</p>

<section role="tabpanel" aria-labelledby="workTab" id="work" class="tabcontent hidden">
<h3>Work contact information</h3>
<p>Lots of input fields that do not matter for the purposes of demonstrating tabs.</p>

<section role="tabpanel" aria-labelledby="otherTab" id="other" class="tabcontent hidden">
<h3>Other Contact Information</h3>
<p>Lots of input fields that do not matter for the purposes of demonstrating tabs.</p>


And we have arrived at our destination, “tab widget city”. A couple of years ago we would have said that the place is a bit of a mess, with inconsistent implementation and assistive technology support making it unreliable and confusing for the user. Today we can confidently say it is a more accessible place with the ARIA markup. But there is still room for improvements, both in terms of the authoring consistency and how assistive technologies take advantage of that markup.

Accessibility requires a lot of players to do their part but without the necessary semantic information that ARIA provides we never give user agents a chance to create a better world for those who need to access web content in an alternative and creative fashion.

Also, don’t forget that you can use Visual ARIA to visually display ARIA usage during development to aid in this process.

Co-authors: Birkir Gunnarsson from Deque Systems, Bryan Garaventa from SSB BART Group, Lèonie Watson from The Paciello Group (TPG), and Matt King from Facebook.



6 thoughts on “From HTML to ARIA Tabs, A Travelog

  1. Great article! The only thing I miss, is a tab stop on the tab panel.
    When tabbing away from the tab list, one currently lands on the next focusable item, in or below the currently active tab panel. In this case, focus moves to the submit button, which has ignored the tab panel contents.

    Yes, in JAWS you are supposed to press Jawskey+alt+m to move to the tab panel, but this is a complicated keystroke and tab is more natural

    Nicolas Hoffmann provides a solution in his example at
    What do you think of this? For the rest, his markup is similar to yours. Would you consider adopting Hoffmann’s variant?


  2. Hi,
    I agree, the way that we have implemented the tab control is to illustrate required attributes and functionality according to the spec, and the use of tabindex=0 on role=tabpanel isn’t actually required, though it is helpful on desktops for keyboard only users. We are currently considering adding the use of tabindex in this way as a best practice for ARIA authoring guidance. For example, this is already implemented in the ARIA tabs at
    Kind regards,


  3. Great article and very informative! I have a few questions:

    1. Why do modern screen readers still announce a tab as a list item if it already knows that it is a tab? Adding the role “presentation” to each list item feels like a hack that it is not very intuitive to developers. Shouldn’t this be handled by assistive technologies automatically?

    2. A common pattern on mobile is to make the whole navigation look and behave like tabs. It’s like the entire application is a big tab widget. Native apps like Twitter, Google Plus, Facebook and LinkedIn all have this pattern. Some apps choose not to qualify their nav items as tabs. For example, Facebook app call each tab as a “button”, while Twitter and even Facebook Messenger App, qualify them as tabs. The focus behavior on each is different, some of them move the focus to the main content after selecting the item (Facebook and Twitter), while others don’t (Messenger, and LinkedIn). My understanding is that we shouldn’t move focus as stated by this article. But when it comes to web apps, what is the recommended approach from the point of view of W3C? Does it change anything the fact that the tabs are also for navigation purpose? Do we also need to add an extra “navigation” role to the “tablist” element?

    Once again, thanks for the great article.


    1. Hi,
      Regarding the HTML LI elements, the primary reason for the use of role=presentation is to prevent them from becoming orphaned LIs in the accessibility tree, which occurs when role=”tablist” is added to the UL element. When this happens, the structure is no longer a list in the accessibility tree, but a tablist which does not include child LI elements, so these then become orphaned. Basically, then it appears to ATs that there are a bunch of LI elements alone in the page with no parent UL element to group them. The role of ‘presentation’ nullifies these extra elements in the accessibility tree. It can be argued that the browsers should actually be doing this automatically, which is something that has been brought up already. In the meantime though, it is safest to just add this attribute on such elements to keep the structure clean for ATs.

      Regarding the mobile tabs, it’s important to note that the word “tab” is a loaded term. In ARIA terms, the only time that ARIA Tab attributes should be used is when you have a client side widget that dynamically renders associated panels of content within the same page, and not at any other time. This is different from an accordion where content is rendered inline with the triggering elements, which should not include ARIA Tab markup, because the paradigm for Tabs is to group all actual Tab controls within one Tablist container, and all Tabpanel content in another container outside of the Tablist. An accordion is different in that the dynamic panel is rendered inline within the primary container, making this separation impossible to achieve. So an accordion should instead be marked up using native buttons or role=button elements in combination with aria-expanded plus a surrounding heading if possible, which is the most intuitive way to represent this functionality. You can see an example of this at

      Also, ARIA Tab attributes should never be used on elements that open different browser pages, because these are standard links, regardless what they are styled to look like visually.

      A simple page toggle is when you have an HTML button or focusable role=button on the page that includes aria-expanded, and when activated, focus can be moved into the controlled content, then when the controlled content is closed focus moves back to the triggering element. These can appear anywhere on the page and are separate from tabs and accordions, because they can be either standalone controls or grouped, which is the concept you are describing for the most part within header navigations.


  4. The second and third tab panels are inaccessible without JS. You could simply add the `tabindex=”-1″` to the tabs and the ‘hidden’ class to the panels with JS to make it nojs friendly, wherein it will fall back to standard links to targets behavior.


    1. That’s true, though it’s important to note that the majority of complex widget types will not work correctly without JavaScript enabled.

      The stance of the W3C is that JavaScript can easily be implemented in such a way as to ensure accessibility when programmed properly, so there is no reason why JavaScript should be disabled, because this will actually prevent most of these widgets from working at all.

      Examples of this include simulated listboxes, sliders, menus, simulated radios, trees, interactive grids, autosuggest comboboxes, amongst others.

      In regard to tabs like this, the ability to use JavaScript to dynamically pull in content from other servers and load it within the same page is supported with this paradigm, but it will not work without the use of JavaScript.

      So it’s a misnomer that all dynamic technologies have to be made to work without JavaScript enabled to ensure accessibility, because many of them cannot work at all without this.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s