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

wai-tabs

v0.0.4

Published

A simple Tabs widget skeleton, built with accessibility in mind - on top of `tabindex`, `aria-*` and `role` attributes.

Downloads

7

Readme

WAI Tabs ⛳

A simple Tabs widget skeleton, built with accessibility in mind - on top of tabindex, aria-* and role attributes. It's a bare minimum, but lets you construct a fully functional module in minutes with little to no effort at the same time, if the correct CSS styles were provided.

Why

  1. There are tons of similar plugins available, but usually we don't need an extra functionality or dive into a setup complexity, these offer.
  2. This plugin lets you ensure that you didn't forget to add all required attributes to match WAI-ARIA Tabs Pattern.
  3. Ability to use any HTML tags as controls besides <button>.
  4. You can use as many wrappers as you need for the navigation items or panels - this will not affect the behavior.
  5. Tab panels can be added in a different order than tabs - it doesn't matter since the script queries the panels by the id attribute.

How it works

Basically, it changes certain attributes, but also provides a simple API for extending: it doesn't add any classes, modify styles or change other attributes except tabindex, aria-selected and aria-hidden, so you need to pass a custom function/hook to the mount method if you want to change the HTML DOM dynamically - please see Configuration. You may ask: "Why the script doesn't add hidden attribute to panels?" Well, because sometimes we don't need to entirely hide the panels, but toggle the CSS visibility property instead to keep the widget height constant, relying on the tallest pane, and using CSS transitions.

It utilizes the following HTML Attributes:

  • role="tablist" is used as an entry selector. This can be changed - please see Configuration, but still, this attribute is considered required https://w3c.github.io/aria/#tablist
  • role="tab" is used as a selector to query all navigation items.
  • aria-orientation ("horizontal"/"vertical") attribute is for internal handling the key codes correctly while using keyboard. It's optional, if your tabs list is horizontal.
  • tabindex ("0"/"-1") to make elements focusable or restrict focus instead.
  • aria-selected ("true"/"false") see https://w3c.github.io/aria/#aria-selected Additionally, aria-selected="true" determines an initial index that's utilized internally while using arrow keys to focus on a tab.
  • aria-controls tab attribute along with the tab panel id to query all associated pane elements.
  • aria-hidden is used to mark the pane as hidden to screen readers.

So, you should add attributes stated above to let the widget work. The script itself doesn't assert custom error messages to keep it lightweight.

Installation

npm i wai-tabs

then

import WAITabs from "wai-tabs"

Configuration

The very basic usage

new WAITabs().mount()

The script takes care about all instances on the page, querying elements by '[role="tablist"]' selector by default.

A minimal CSS might look like this:

[role="tab"][aria-selected="true"] {
  background-color: blue;
  color: white;
}
[role="tabpanel"][aria-hidden="true"] {
  display: none;
}

And Tabs HTML:

<div role="tablist" aria-orientation="horizontal">
  <button id="tab-1" role="tab" aria-selected="true" tabindex="0" aria-controls="panel-1">Tab 1</button>
  <button id="tab-2" role="tab" aria-selected="false" tabindex="-1" aria-controls="panel-2">Tab 2</button>
  <button id="tab-3" role="tab" aria-selected="false" tabindex="-1" aria-controls="panel-3">Tab 3</button>
</div>
<div id="panel-1" role="tabpanel" tabindex="0" aria-labelledby="tab-1" aria-hidden="false">
  Panel #1
</div>
<div id="panel-2" role="tabpanel" tabindex="-1" aria-labelledby="tab-2" aria-hidden="true">
  Panel #2
</div>
<div id="panel-3" role="tabpanel" tabindex="-1" aria-labelledby="tab-3" aria-hidden="true">
  Panel #3
</div>

Advanced usage

Since we might need to have more control over the look and behavior by using custom classes or change the HTML DOM dynamically, the code might look like this:

/**
 * This function invokes per a separate instance initialization and
 * may optionally return an object with two optional properties
 * - callbacks "onSelect" and "onTabKeyFocus"
 */
const useInstance = (singleInstance) => {
  const {
    wrapper,  // [role="tablist"] element by default or <div class="my-tabs">...</div> in our case
    navItems, // an array of [role="tab"] elements
    panes,    // an array of associated [role="tabpanel"] elements
    getIndex, // a function/method - get an internal active tab's index
    selectTab // a function/method - programmatically select a desired tab, passing a certain index as an argument
  } = singleInstance;

  // Adding custom data attribute just as an example
  const initialIndex = getIndex();
  panes[initialIndex].dataset.currentPane = "true";

  return {
    /**
     * Fires when a tab is selected on the "click" or "keydown"
     * event or after invoking the "selectTab" method (event as
     * a second argument will be undefined).
     */
    onSelect: (index, event) => {
      console.log(event.type === "click" ? `Clicked the tab #${index + 1}` : `Pressed the tab #${index + 1}`);

      navItems.forEach((navItem, i) => {
        // I personally use Tailwind, so I need to add/remove a certain class
        navItem.classList[i === index ? "add" : "remove"]("text-blue-400", "border-b-blue-400");

        // Change pane's "data-current-pane" attribute
        panes[i].dataset.currentPane = (i === index).toString();
      })
    },
    /**
     * Fires when focused on a tab, but only if keyboard
     * keys such as arrows, Home, End were used
     */
    onTabKeyFocus(index, event) {
      console.log(`The tab #${index + 1} is in focus!`, event.type);
    }
  };
};

new WAITabs(".my-tabs").mount(useInstance);

Run a local dev server

npm i && npm start