jQuery Accessible tab panel system, using ARIA

Find/fork me on Github

Other accessible plugins

The jQuery plugin will transform a simple list of anchors to contents into a fantastic-shiny tabpanel system, using ARIA.

Here are how-to-use and some examples of this plugin.

Robust: as it relies on a normal HTML structure (a list of links with internal anchors), even if the JavaScript isn’t loaded, your page will work, it’s the miracle of progressive enhancement.

Accessible: it is based on ARIA Design Pattern for tabpanel.

Keyboard navigation is supported too, here are keys.

If you focus in the tabs “buttons”:

  • use Up/Left to see previous tab,
  • use Down/Right to see next tab
  • use Home to see first tab (wherever you are in tab buttons)
  • use End to see last tab (wherever you are in tab buttons)

If you focus in a tab content:

  • use Ctrl Up/Left to set focus on the tab button for the currently displayed tab
  • use Ctrl PageUp to set focus on the previous tab button for the currently displayed tab
  • use Ctrl PageDown to set focus on the next tab button for the currently displayed tab

Warning: Ctrl+PageUp/PageDown combination could activate for some browsers a switch of browser tabs. Nothing to do for this, as far as I know (if you have a solution, let me know).

Its weight is only:

  • 13kb (development, readable by humans);
  • 4kb (minified, readable by machines);
  • 1kb minified and gzipped (readable by… mutants‽‽)

It takes more time to read the documentation than to install and use the plugin.

No license problem: it uses MIT license, so it’s free, open-source and you can do whatever you want with it, including commercial use. This permission notice shall be included in all copies or substantial portions of it.

However, it is not prohibited to tell me that you’ve used it, or send me a little “thank you”. ;)

The classes set by default do not add any CSS styles. The only one needed is:

.js-tabcontent[aria-hidden=true] { display: none; }

just to hide unactive tabs. So basically, you can do whatever you want, including for responsive.

Several websites are using it:

And some others that are in development.

Last updates

21st of September 2017: added an option to disable fragments (see example below), as kindly requested by @dennisfrank .

14th of April 2017: added data-selected="1" attribute to open a tab by default (read the example).

9th of April 2017: linted and reindented code properly.

31st of March 2017: created a special version of the script for nested tabs thanks to @dameert, dedicated file is on the repositery: jquery-accessible-nested-tabs.js (with stricter selectors).

15th of February 2017: fixed an issue to avoid WordPress jQuery conflict mode thanks to @floriantiar, and added the version number in JavaScript file, as suggested by @juliemoynat.

You can see older updates if needed.

Several demos and instructions

How to use it

Just download the plugin:

jQuery accessible tabs using ARIA on Github

And use jQuery of course :)

You may also use npm command: npm i jquery-accessible-tabpanel-aria.
You may also use bower: bower install jquery-accessible-tabs-aria.

Then, follow the conventions given in this minimal example (in bold).

  1. use classes needed (js-tabs, js-tablist, js-tablist__item, js-tablist__link, js-tabcontent)
  2. be careful to use the convention in the links a href="#id_first" id="label_id_first"
  3. check that your anchors are working.
  4. for accessibility purposes (for VoiceOver), the plugin has to give focus to hx (h2, h3, h4, etc.) in tab contents.

For Hx, you have two cases:

  1. There aren't any in your js-tabcontent’s, so specify in data-hx attribute (will be added with class="invisible", which means visually hidden);
  2. There are some, just tell it to the plugin using data-existing-hx attribute.

Example without hx:

<div class="js-tabs">
  <ul class="js-tablist" data-hx="h2">
   <li class="js-tablist__item">
    <a href="#id_first" id="label_id_first" class="js-tablist__link">1st tab</a>
   </li>
   <li class="js-tablist__item">
    <a href="#id_second" id="label_id_second" class="js-tablist__link">2nd tab</a>
   </li>
  </ul>
 <div id="id_first" class="js-tabcontent">
   here the content of 1st tab
 </div>
 <div id="id_second" class="js-tabcontent">
   here the content of 2nd tab
 </div>
</div>

Example with hx:

<div class="js-tabs">
  <ul class="js-tablist" data-existing-hx="h2">
   <li class="js-tablist__item">
    <a href="#id_first" id="label_id_first" class="js-tablist__link">1st tab</a>
   </li>
   <li class="js-tablist__item">
    <a href="#id_second" id="label_id_second" class="js-tablist__link">2nd tab</a>
   </li>
  </ul>
 <div id="id_first" class="js-tabcontent">
   <h2>title</h2>
   here the content of 1st tab
 </div>
 <div id="id_second" class="js-tabcontent">
   <h2>other title</h2>
   here the content of 2nd tab
 </div>
</div>

Then call jQuery and the plugin in your page. Yes, that’s all.

How to style it (nicely)

Once you set up the code with your content, style it before activating JavaScript: so it will be nice even if there is no JavaScript.

For example, just imagine you are on poor mobile connexion, and the JavaScript hasn’t (yet) loaded. Or it can be disabled.

You should add classes to the source, and use them this way:

[role="tablist"].my-style {…}
            

Then activate JavaScript.

Basically, you should rely on ARIA attributes, so styles will be applied only if JavaScript is loaded and well-executed.

Usually, I minify my CSS, but for this example, I didn’t.
Go into the CSS and have a look (search “styles for tabs, examples”), and also in the breakpoints.

I advise you to set it up like this, for example for “How to use it”:

/* 
 * Styles tabs 1/2/3
 */
 
/* Styles without JS */

.puce-tab__text {
  font-weight: bold; 
}
.puce-tab__number {
  display: inline-block;
  font-size: 1.5em;
  width: 1.5em;
  height: 1.5em;
  background: #882525;
  color: #fff;
  border-radius: 50%;
  font-weight: normal;
}



/* Styles with JS */
[role="tab"].puce-tab {
  opacity: .6;
  -webkit-transition: all .5s ease;
     -moz-transition: all .5s ease;
       -o-transition: all .5s ease;
          transition: all .5s ease;
}
[aria-selected="true"].puce-tab {
  opacity: 1;
  font-size: 1.7em;
  width: 1.7em;
  height: 1.7em;
}
            

If you need the class invisible, here is an example:

.invisible {
  border: 0;
  clip: rect(0 0 0 0);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  width: 1px;
}

The plugin has another feature: if you don’t like styling on role attributes, it can generate classes for you only for styling tabs when they are activated.

Look at the last tabs of this page, here is the source:

<ul … data-tabs-prefix-class="last-tabs">

The data-tabs-prefix-class will add classes on each elements:

<ul … class="… last-tabs-tabs__list">
  <li … class="… last-tabs-tabs__item">
    <a … class="… last-tabs-tabs__link">…

So with data-tabs-prefix-class="last-tabs" you have:

  • last-tabs-tabs__list for styling the ul;
  • last-tabs-tabs__item for styling the li;
  • last-tabs-tabs__link for styling the a.

These classes could be used for styling tabs when they have been ARIA-ised (when JavaScript is loaded, if you prefer).

Bonuses: “fragment” management and “in-page link to tab”

As you may notice (or not), now the plugin adds a fragment in the URL when you click or select a tab using the keyboard. It is cool when you want to copy/paste the link of the page you are reading, and the opened tab will be the good one!

If you need to make a link on your page to a id that is on a tab panel, the plugin will detect it and display the good tab, only for you.

If you want to test, open a new window, and copy/paste this:
https://a11y.nicolas-hoffmann.net/tabs/#light_free

If you need to make a link on your page to a id that is in a tab panel, the plugin will detect it and display the good tab, only for you.

If you want to test, open a new window, and copy/paste this:
https://a11y.nicolas-hoffmann.net/tabs/#license-cool-anchor

Bonus: tab panel opened by default

This tab is available but sad, as the second is opened by default, using data-selected="1".

If you need a tab to be opened by default, it is possible, using data-selected="1" on the js-tablist__link you need to open.

Other tabs are still available, here are the rules for this feature:

  • The fragment detection has always priority on this feature;
  • If there are several data-selected="1" put on tabs (which does not make sense and should never happen), the first one will be used;
  • If the data-selected="1" attribute is set on a disabled tab, of course it won’t be selected.

This tab is also available.

Other bonuses: “disabled” tab management, and disabled fragments

No content

To disable a tab, you only have to put aria-disabled="true" on the a tag. Example:

<li class="js-tablist__item tabs__standard__li">
  <a href="#disabled2" id="label_disabled2" class="js-tablist__link tabs__standard__a" aria-disabled="true">Another disabled tab</a>
 </li>
     

The plugin will detect it, and adapt everything:

  • The keyboard management (test on this set of tabs);
  • The selection of first “non-disabled” tab;
  • Etc.

Here is a simple piece of styling:

[aria-disabled="true"], 
[aria-disabled="true"]:hover {
  background-color: #ddd;
  color: #666;
  pointer-events: none;
  /* for old IEs or browser that don’t support pointer-events */
  cursor: not-allowed;
}

No content, I said.

If you need to disable fragments that are automatically added to the URL, it is possible.

You just need to add data-tabs-disable-fragment="1" to the div class="js-tabs">.

<div class="js-tabs" data-tabs-disable-fragment="1">

And that’s all. :)

Older updates

17th of November 2016: added a “disabled tab” feature (see example below), on an idea submitted by @gdurelle.

17th of April 2016: fixed two bugs in case of nested tabs.

6th of April 2016: added data-tabs-generated-hx-class optionnal attribute. This option is useful to specify the class that will be applied to the hx generated if you use the attribute data-hx. If this attribute is not specified, the class invisible will be used.

21st of March 2016: this plugin is available on bower: bower install jquery-accessible-tabs-aria

11th of January 2016: this plugin is available on NPMjs.com

24th of August 2015: complete refactoring: