Accessible Tab Widget built with ARIA
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

Accessible Tabbed Interfaces

A script to progressively enhance sectioned content into an accessible tabbed interface.

How to use

To help facilitate the simplest integration with your code base, the necessary markup has been boiled down to a wrapping element with a data-atabs attribute to serve as the Tab Widget container. Additionally, tabs, and tabpanels can be designated via different markup patterns to help suit your needs.

Example Setup

<div data-atabs> <!-- necessary wrapping element -->
  <!-- Panel method 1 -->
  <div data-atabs-panel 
    data-atabs-tab-label="Tab label goes here">
    <!-- all panel content goes here -->

  <!-- Panel method 2 -->
  <section data-atabs-panel>
    <h# data-atabs-label>
        The text/markup injected the panel's 
        associated role="tab" element.
  <!-- repeat as necessary -->
<!-- ... -->
<script src="index.js"></script>
  var widget = '[data-atabs]';
  var els = document.querySelectorAll(widget);

  // Generate all Tab Widget instances
  for ( var i = 0; i < els.length; i++ ) {
    var nTabs = new ARIAtabs( els[i] );

Injecting content into the Tab Widget

Once a new instance of a Tab Widget has been created, the addTab function can be called from outside the script. Using this function, you can reference elements in the DOM (by id) that are not in the data-atabs wrapping element, and move them into the Tab Widget, creating a tab and tabpanel for the content. This may be useful if you need to create a Tab Widget, but you may not have full control over the HTML of your document.

For instance:

<script src="index.js"></script>
  var tabInstance = '[data-atabs]';
  var els = document.querySelectorAll(tabInstance);
  var injectContent = document.getElementById('inject-content');
  var cloneContent = injectContent.cloneNode(true);
  var allTabs = [];

  // Generate all tab instances
  for ( var i = 0; i < els.length; i++ ) {
    var nTabs = new ARIAtabs( els[i] );


  // remove the original instance of the external content from the document.

  // Inject the external content into a particular
  // tab, captured in the allTabs var.
  allTabs[1].addTab(cloneContent, 'Tab label', 'add-a-class');

Tab Widget attributes & options

The Tab Widget script runs through the markup looking for specific data-atabs-* attributes to use as hooks to modify the original markup and generate the final component. Here are the data attributes that are recognized by this script.

  • data-atabs
    The primary hook. This necessary attribute is used to contain the final Tab Widget.

  • data-atabs-panel
    Designates that an element should serve as a tabpanel. If given the value of "default", the script will set this tabpanel and associated tab to be active, instead of automatically first tab and tabpanel. If multiple data-atabs-panel attributes have the value of "default", only the first one will be respected.

  • data-atabs-tab-label
    Also used on the element that will be a tabpanel, this attribute indicates that the generated tab should use its value as the tab's label. The value of data-atabs-tab-label takes precedents over using the content of the tabpanel's heading when generating the tab.

  • data-atabs-heading
    Place this attribute on the element that serves as the heading within the tabpanel. Unless a data-atabs-tab-label is used on the tabpanel, this heading will serve as the accessible name for the generated tab. By default, elements with the data-atabs-heading attribute will be removed after their content has been used for the generated tab, unless the value of "keep" is set, e.g. data-atabs-heading="keep. Only the first instance of an element with this attribute will be recognized by the script.

  • data-atabs-toc
    Without JavaScript, a table of contents (TOC) can provide easy access to different sections of a document that would have otherwise been part of the Tab Widget. With JavaScript available, the TOC isn't as necessary. Providing this attribute with the id of the TOC parent element will remove the TOC from the DOM.

  • data-atabs-manual
    By default, when keyboard focus is set to a tab, its associated tabpanel will open by default, and the tab will be set to the selected state.

    If this attribute is set to the data-atabs wrapper of any Tab Widget in a document, it will make all Tab Widgets require manual activation of a tab to reveal its associated tabpanel. The reason this globally affects Tab Widgets is to mitigate any possibility of an inconsistent user experience between different Tab Widgets in the same document.

  • data-atabs-orientation
    If this attribute is set to the data-atabs wrapper element, and it's value is set to "vertical", then it will add aria-orientation="vertical" to the tablist. As aria-orientation is not well supported across all screen readers, this does not presently have any effect on the current functionality of the Tab Widget.

User Experience

Tab Widgets are a type of show/hide component, and the manner in which you interact with a Tab Widget will dictate the experience.

For mouse and touch users, the show/hide functionality is initiated by interacting with the tabs within the tablist. Activating a tab will reveal the tabpanel its associated with, while also deactivating the previously active tab, and hiding its tabpanel.

For keyboard users, and screen reader users in forms mode, a Tab Widget will automatically select and reveal the contents of the focused tab as a user navigates the tablist with arrow keys.

A option is available (data-atabs-manual) to require manual activation of tabs. This is not necessarily preferred behavior of a Tab Widget, but there may be instances where it's required due to performance issues.

The ARIA Authoring Practices notes:

It is recommended that tabs activate automatically when they receive focus as long as their associated tab panels are displayed without noticeable latency. This typically requires tab panel content to be preloaded. Otherwise, automatic activation slows focus movement, which significantly hampers users' ability to navigate efficiently across the tab list.

This script has been tested to work with mouse, touch, and keyboard devices. Each input device has also been re-tested while running various browser and screen reader pairings.

FYI: More information concerning expected functionality and screen reader announcements will be linked to from here.

Dependencies and known issues

There are no dependencies for this script. Any necessary polyfill (for IE11) is included in the JavaScript.

There are some issues with how screen readers interact with Tab Widgets. Where possible, this script will attempt to mitigate issues with screen readers, but not if it were to create additional bugs if the original but were to be fixed:

Additional Reading

License, Thank yous & Such

This script was written by Scott O'Hara: Website, Twitter.

It has an MIT license.

Special thanks to Josh Drumm and Chris Ferdinandi for helping me with some JavaScript refactoring and helper functions.

Thanks to Léonie Watson, Adam Campfield, Ryan Adams, and many others who provided excellent user feedback.

Use it, modify it, contribute to it to help make your project more accessible :)