Tabs

Tabs, which are very similar to accordions, are also common web design structures used to organize and hide content, so as not to overwhelm the user. Tabs also additionally require JavaScript to show/hide things based on a user click or focus event.

The key to making tabs accessible is to toggle CSS display properties and some ARIA states on user click or focus events (e.g. toggle aria-selected, tabindex, and display:none/block; and/or aria-hidden='true/false').

Best Practices

Tabbed content usually consist of two main parts. First, they consist of the tabs themselves that control the visibility of some area of content below. Second is the content controlled by the tabs. In general, we refer to the former as tabs, and the latter as tab panels.

Tab Controls

  • Each tab control is set up as a link inside an unordered list structure. The unordered list should have an aria role='tablist'. Using links rather than buttons for tab controls seems to be common practice, though we could see an argument made for making tab controls buttons instead.

  • All the tab controls have an aria role='tab'.

  • Each tab control has a unique id and href which is associated with a tab panel’s id.

  • Each tab link also has a unique aria-controls attribute which references its tab panel’s id ({name-of-fruit}).

  • Every tab control has an aria-selected attribute that is set to either true or false. If true, the tab control has been clicked or focused on, and the tab panel associated with it should be visible. If false, the tab control is not selected, and its corresponding tab panel is hidden.

  • Also, for the tab controls that are not currently selected, we place tabindex='-1' on them to take them out of the tab order. If there are 10-12 tab controls on your web page, user’s of assistive technology will now not have to tab through all of them to reach the content in the tab panel.

Tab Panels

  • A CSS class of .hidden or .current is toggled on each tab panel when the active tab control changes focus, so that when a panel should be hidden, display: none; is applied to the panel, and when it should be visible, display: block; is applied instead. When a panel has display: none; applied, the tab panel is neither visible nor perceivable by screen reader, and assistive technologies will skip over this content.

  • All the tab panels have an aria role='tabpanel'.

  • Each tab panel also has an aria-labelledby attribute which serves a similar purpose to how the aria-controls acted in our accordions example.

    • The aria-labelledby attribute is set to the id of the corresponding tab control, such as: aria-labelledby='tab-{#}' to give users of assistive technology a more comprehensive idea of how the tabs and tab panels are linked.

For details on what we’ve done with JavaScript to make the tabs work, see the comments/annotations we have made within the JS code snippet itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<div class="tabbed-content">
  <ul class="tabs-list" role='tablist'>
    <li class="current">
      <a aria-controls="apples"  aria-selected="true" href="#apples" id="tab-apples" role='tab' class='tab selected'>
        Apples
      </a>
    </li>

    <li>
      <a aria-controls="bananas" href="#bananas" aria-selected='false' id="tab-bananas" role='tab' tabindex='-1' class='tab'>
        Bananas
      </a>
    </li>

    <li class='last'>
      <a aria-controls='pears' aria-selected="false" href="#pears" id="tab-pears" role='tab' tabindex='-1' class='tab'>
        Pears
      </a>
    </li>
  </ul>

  <div aria-labelledby="tab-apples" class="tab-panel current"  id="apples" role='tabpanel'>
    <h2>Apples</h2>
    <p> Lorem ipsum dolor sit amet, urna class vestibulum tincidunt atque, habitasse sit wisi erat dapibus. Vitae curae natoque a, donec nulla conubia in mollis. Sapien pede in tortor, lectus neque vitae in et, vitae aliquam eget orci at, non turpis faucibus id morbi. Elit tempor turpis donec inceptos, fringilla arcu sollicitudin ligula magna, sed justo viverra lacus erat, vestibulum id in justo nulla. Viverra dui leo donec, aptent deserunt nostra magnis lobortis, id ultrices ac aenean, interdum vestibulum rhoncus phasellus libero.</p>
  </div>

  <div aria-labelledby="tab-bananas" class="tab-panel hidden" role='tabpanel' id="bananas">
    <h2>Bananas</h2>
    <p>Lorem ipsum dolor sit amet, urna class vestibulum tincidunt atque, habitasse sit wisi erat dapibus. Vitae curae natoque a, donec nulla conubia in mollis. Sapien pede in tortor, lectus neque vitae in et, vitae aliquam eget orci at, non turpis faucibus id morbi. Elit tempor turpis donec inceptos, fringilla arcu sollicitudin ligula magna, sed justo viverra lacus erat, vestibulum id in justo nulla. Viverra dui leo donec, aptent deserunt nostra magnis lobortis, id ultrices ac aenean, interdum vestibulum rhoncus phasellus libero.</p>
  </div>

  <div aria-labelledby="tab-pears" class="tab-panel hidden" id="pears" role='tabpanel'>
    <h2>Pears</h2>
    <p>Lorem ipsum dolor sit amet, urna class vestibulum tincidunt atque, habitasse sit wisi erat dapibus. Vitae curae natoque a, donec nulla conubia in mollis. Sapien pede in tortor, lectus neque vitae in et, vitae aliquam eget orci at, non turpis faucibus id morbi. Elit tempor turpis donec inceptos, fringilla arcu sollicitudin ligula magna, sed justo viverra lacus erat, vestibulum id in justo nulla. Viverra dui leo donec, aptent deserunt nostra magnis lobortis, id ultrices ac aenean, interdum vestibulum rhoncus phasellus libero.</p>
  </div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
$(function(){
  var index = 0;
  var $tabs = $('a.tab');

  $tabs.bind(
  {
    // on keydown,
    // determine which tab to select
    keydown: function(ev){
      var LEFT_ARROW = 37;
      var UP_ARROW = 38;
      var RIGHT_ARROW = 39;
      var DOWN_ARROW = 40;

      var k = ev.which || ev.keyCode;

      // if the key pressed was an arrow key
      if (k >= LEFT_ARROW && k <= DOWN_ARROW){
        // move left one tab for left and up arrows
        if (k == LEFT_ARROW || k == UP_ARROW){
          if (index > 0) {
            index--;
          }
          // unless you are on the first tab,
          // in which case select the last tab.
          else {
            index = $tabs.length - 1;
          }
        }

        // move right one tab for right and down arrows
        else if (k == RIGHT_ARROW || k == DOWN_ARROW){
          if (index < ($tabs.length - 1)){
            index++;
          }
          // unless you're at the last tab,
          // in which case select the first one
          else {
            index = 0;
          }
        }

        // trigger a click event on the tab to move to
        $($tabs.get(index)).click();
        ev.preventDefault();
      }
    },

    // just make the clicked tab the selected one
    click: function(ev){
      index = $.inArray(this, $tabs.get());
      setFocus();
      ev.preventDefault();
    }
  });

  var setFocus = function(){
    // undo tab control selected state,
    // and make them not selectable with the tab key
    // (all tabs)
    $tabs.attr(
    {
      tabindex: '-1',
      'aria-selected': 'false'
    }).removeClass('selected');

    // hide all tab panels.
    $('.tab-panel').removeClass('current');

    // make the selected tab the selected one, shift focus to it
    $($tabs.get(index)).attr(
    {
      tabindex: '0',
      'aria-selected': 'true'
    }).addClass('selected').focus();

    // handle parent <li> current class (for coloring the tabs)
    $($tabs.get(index)).parent().siblings().removeClass('current');
    $($tabs.get(index)).parent().addClass('current');

    // add a current class also to the tab panel
    // controlled by the clicked tab
    $($($tabs.get(index)).attr('href')).addClass('current');
  };
});

Live Example

Apples

Lorem ipsum dolor sit amet, urna class vestibulum tincidunt atque, habitasse sit wisi erat dapibus. Vitae curae natoque a, donec nulla conubia in mollis. Sapien pede in tortor, lectus neque vitae in et, vitae aliquam eget orci at, non turpis faucibus id morbi. Elit tempor turpis donec inceptos, fringilla arcu sollicitudin ligula magna, sed justo viverra lacus erat, vestibulum id in justo nulla. Viverra dui leo donec, aptent deserunt nostra magnis lobortis, id ultrices ac aenean, interdum vestibulum rhoncus phasellus libero.

Tools & Resources

Other Live Examples of Accessible Tabs