npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

a11y_tab_widgets

v2.0.1

Published

ES5 script to create accessible tab widget components

Downloads

8

Readme

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 required markup is as lean as possible.

Minimum Setup

<div data-atabs>
  <div data-atabs-panel 
    data-atabs-tab-label="Tab label goes here">
    <!-- all panel content goes here -->
  </div>
  <section data-atabs-panel>
    <h# data-atabs-label>
      <!-- 
        The text/markup injected the panel's 
        associated role="tab" element.
      -->
    </h#>
  </section>
  <!-- repeat as necessary -->
</div>
<!-- ... -->
<script src="index.js"></script>
<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] );
  }
</script>

data-atabs attributes & options

The script runs through the minimum markup looking for specific data-atabs-* to use as hooks to modify the original markup and generate the final Tab Widget.

  • data-atabs
    The primary hook. This attribute is used to contain the final Tab Widget.
  • 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 will remove the TOC from the DOM.
  • data-atabs-automatic
    If this attribute is set to the data-atabs wrapper of any Tab Widget in a document, it will make all Tab Widgets automatically reveal the tabpanel associated with the currently focused tab element. The reason this globallly affects Tab Widgets is to reduce any possibility of an inconsistent user experience between different Tab Widgets.
  • 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 and modify the arrow keys from left and right to up and down to move focus through the tabs within the tablist.
  • data-atabs-swap-orientation
    TODO Use this attribute on the data-atabs wrapper and provide it a UNIT value to indicate when a Tab Widget's tablist should change from "vertical" to "horizontal" orientation for responsive design interfaces.
  • 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 the 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.

Injecting tabpanels 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 point to elements in the DOM that are not wrapped in the data-atabs element to inject them into the Tab Widget.

For instance:

<script src="index.js"></script>
<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] );

    allTabs.push(nTabs);
  }

  // remove the original instance of the external content from the document.
  injectContent.parentNode.removeChild(injectContent);

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

User Experience

The manner in which you interact with a Tab Widget is dependent on your input device. Not all devices/assistive applications are listed here, but the following will give you a baseline of expectations if when testing this script, or comparing your own Tab Widget.

Mouse / Touch

Clicking or tapping a tab will set that tab to its selected state, and reveal its associated tabpanel, while deselecting and hiding the previously selected tab and its tabpanel.

Mouse / Touch + Screen Reader

If using a mouse while also using NVDA with the setting "Report role when mouse enters object", NVDA will announce "Tab. Accessible Name."

If using iOS with VoiceOver enabled, and exploring by touch, a tab should announce itself as "Accessible name. Tab. Number of Numbers". If the touched tab is currently active VoiceOver will announce "Selected" prior to the accessible name.

Keyboard

When interacting with a Tab Widget with a desktop or laptop keyboard, one can use the Tab key to navigate to the tablist. Keyboard focus will highlight the tab that is currently active.

If the tablist is horizontally orientated, using the Left and Right arrow keys to will navigate to the previous and next tabs in the tablist. Keyboard focus will loop from the last tab to the first, and vice versa. If the tablist is vertically oriented, Up and Down arrow keys will navigate the tabs. Note: vertically oriented tablists should have the attribute aria-orientation="vertical.

If a Tab Widget has a data-atabs-automatic set to it, then any Tab Widgets in the current document will automatically load the associated tabpanel of a tab when it receives focus via arrow keys.

Keyboard + Screen Readers

This section coming soon...

VoiceOver: MacOS

VoiceOver: iOS

Android Accessibility Suite (TalkBack)

JAWS

NVDA

Narrator

Dependencies

There are no major dependencies for this script. The WICG :focus-visible polyfill is used in the demonstration page, and has a class included in the CSS file. However, it is up to you if you would like to download the polyfill yourself.

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 for helping me with some JavaScript refactoring.

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