Skip to content
Zero dependency plain JavaScript module for WCAG compliant tablists. Also great for accordions.
JavaScript
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
examples
src v1.1.0 preparatory code: added new options + handle aria-disabled Jan 11, 2020
.babelrc.js change build tool to webpack instead of parcel + update version to 1.1.0 Jan 11, 2020
.gitignore initial work Jan 9, 2020
CHANGELOG.md
LICENSE Initial commit Jan 8, 2020
README.md
package-lock.json change build tool to webpack instead of parcel + update version to 1.1.0 Jan 11, 2020
package.json change build tool to webpack instead of parcel + update version to 1.1.0 Jan 11, 2020
webpack.config.js

README.md

Aria Tablist

npm version gzip size

Dependency-free plain JavaScript module for WCAG compliant tablists. Also great for accordions.

Try out the examples

Key design goals and features are:

  • multi and single select modes
  • horizontal and vertical modes: Adjusts arrow key usage for moving focus between tabs
  • progressive enhancement: Allows for only the tab and panel relationship to be indicated in the DOM, and adds role and aria attributes automatically as needed
  • accessibility: Follows the WCAG spec
  • compatibility: Broad browser and device support (IE9+)
  • starting states: Can use aria-selected="true" to indicate which tab(s) should be enabled by default.
  • deletion: Can enable tab (and panel) deletion

Installation / usage

Grab from NPM and use in a module system:

npm install aria-tablist
import AriaTablist from 'aria-tablist';
new AriaTablist(document.getElementById('tablist'));

Or grab the minified JavaScript from unpkg:

<script src="https://unpkg.com/aria-tablist/dist/aria-tablist.min.js"></script>

The module relies entirely on standard attributes: it sets the role on elements if it needs to, aria- attributes for indicating behaviour to screen readers, and relies on setting and removing hidden="hidden" to toggle element visibility. This means that you can use all of your own class names and styling, and the module won't override them.

HTML Requirements / Progressive Enhancement

When the module is called on an element, the following steps are taken:

  1. The module will look for elements with role="tab" set.
  2. If none are found, all direct children will be processed.
  3. For each assumed tab, the module will check for a matching tabpanel by:
    1. Checking for an aria-controls attribute on the tab, and searching for an element with a matching id.
    2. If the tab has an id, searching for an element with an aria-labelledby attribute that matches that id.
  4. For any tabs that were processed where a matching panel was not found, if they had role="tab" set, the role attribute will be removed to prevent confusion to screen reader users.
  5. The found tabs and associated panels will then have the relevant role and aria- attributes set automatically.

This means your HTML only needs to indicate the relationship between the tabs and panels, and the module will handle the rest:

<div id="tabs">
    <div id="tab-1">Panel 1</div>
    <div id="tab-2">Panel 2</div>
    <div id="tab-3">Panel 3</div>
</div>

<div aria-labelledby="tab-1">...</div>
<div aria-labelledby="tab-2">...</div>
<div aria-labelledby="tab-3">...</div>

<script>
    new AriaTablist(document.getElementById('tabs'));
</script>

So if you need to cater for users without JavaScript, or if the JavaScript fails to load for whatever reason, there won't be any applicable roles set that would confuse a screen reader user.

You can of course include all of the optimal ARIA attributes straight away if you wish, including indicating which tab should be active by default:

<div id="tabs" role="tablist" aria-label="Tabs">
    <div role="tab" tabindex="-1" aria-controls="panel-1" id="tab-1">
        Panel 1
    </div>
    <div role="tab" tabindex="0" aria-selected="true" aria-controls="panel-2" id="tab-2">
        Panel 2
    </div>
    <div role="tab" tabindex="-1" aria-controls="panel-3" id="tab-3">
        Panel 3
    </div>
</div>

<div role="tabpanel" aria-labelledby="tab-1" hidden="hidden" id="panel-1">...</div>
<div role="tabpanel" aria-labelledby="tab-2" id="panel-2">...</div>
<div role="tabpanel" aria-labelledby="tab-3" hidden="hidden" id="panel-3">...</div>

Options

Most of the functionality is assumed from the included ARIA attributes in your HTML (see the examples). The remaining available options and their defaults are:

{
    /**
     * @description delay in milliseconds before showing tab(s) from user interaction
     */
    delay: 0,

    /**
     * @description allow tab deletion - can be overridden per tab by setting data-deletable="false"
     */
    deletable: false,

    /**
     * @description make all tabs focusable in the page's tabbing order (by setting a `tabindex` on them), instead of just 1
     */
    focusableTabs: false,

    /**
     * @description make all tab panels focusable in the page's tabbing order (by setting a `tabindex` on them)
     */
    focusablePanels: true,

    /**
     * @description activate a tab when it receives focus from using the arrow keys
     */
    arrowActivation: false,

    /**
     * @description value to use when setting tabs or panels to be part of the page's tabbing order
     */
    tabindex: 0,

    /**
     * @description callback each time a tab opens
     */
    onOpen: (panel, tab) => {},

    /**
     * @description callback each time a tab closes
     */
    onClose: (panel, tab) => {},

    /**
     * @description callback when a tab is deleted
     */
    onDelete: (tab) => {},

    /**
     * @description callback once ready
     */
    onReady: (tablist) => {}
}

All component options that accept a Function will have their context (this) set to include the full autocomplete API (assuming you use a normal function: () {} declaration for the callbacks instead of arrow functions).

API

The returned AriaTablist class instance exposes the following API (which is also available on the original element's ariaTablist property):

{
    /**
     * @description the tab elements the module currently recognises
     */
    tabs: Element[];

    /**
     * @description the panel elements the module currently recognises
     */
    panels: Element[];

    /**
     * @description the current options object
     */
    options: Object;

    /**
     * @description trigger a particular tab to open (even if disabled)
     * @param {Number|Element} index - tab index, or tab element
     * @param {Boolean} [focusTab=true] - move focus to the tab before opening
     */
    open(index: Number|Element, focusTab: Boolean = true): void;

    /**
     * @description trigger a particular tab to close (even if disabled)
     * @param {Number|Element} index - tab index, or tab element
     * @param {Boolean} [focusTab=false] - move focus to the tab before closing
     */
    close(index: Number|Element, focusTab: Boolean = false): void;

    /**
     * @description delete a particular tab and its corresponding panel (if deletable)
     * @param {Number|Element} index - tab index, or tab element
     */
    delete(index: Number|Element): void;

    /**
     * @description destroy the module - does not remove the elements from the DOM
     */
    destroy(): void;
}
You can’t perform that action at this time.