js-tab-ui

@yama-dev/js-tab-ui

A lightweight accessible tab UI library for vanilla JavaScript.

Features

Installation

npm install @yama-dev/js-tab-ui

Basic Usage

<div data-ui-tab data-ui-tab-init="overview">
  <div role="tablist" aria-label="Example tabs">
    <button type="button" data-ui-tab-trigger="overview">Overview</button>
    <button type="button" data-ui-tab-trigger="details">Details</button>
    <button type="button" data-ui-tab-trigger="disabled" class="--disabled">
      Disabled
    </button>
  </div>

  <section data-ui-tab-content="overview">Overview panel</section>
  <section data-ui-tab-content="details">Details panel</section>
  <section data-ui-tab-content="disabled">Disabled panel</section>
</div>
import { init } from '@yama-dev/js-tab-ui';

init();

CDN Usage

Manual initialization:

<script src="./dist/js-tab-ui.umd.global.js"></script>
<script>
  window.UiTab.init();
</script>

Automatic initialization:

<script src="./dist/js-tab-ui.auto.global.js" defer></script>

Options

type UiTabOptions = {
  rootSelector?: string;
  triggerSelector?: string;
  contentSelector?: string;
  activeClass?: string;
  disabledClass?: string;
  hiddenAttribute?: string;
  useHiddenAttribute?: boolean;
  orientation?: 'horizontal' | 'vertical';
  activation?: 'auto' | 'manual';
  on?: {
    init?: (detail: UiTabEventDetail) => void;
    change?: (detail: UiTabEventDetail) => void;
  };
};

Defaults:

init({
  rootSelector: '[data-ui-tab]',
  triggerSelector: '[data-ui-tab-trigger]',
  contentSelector: '[data-ui-tab-content]',
  activeClass: '--active',
  disabledClass: '--disabled',
  hiddenAttribute: 'hidden',
  useHiddenAttribute: true,
  orientation: 'horizontal',
  activation: 'auto',
  on: undefined
});

Data Attribute Options

The auto bundle reads these root-level attributes:

<div
  data-ui-tab
  data-ui-tab-active-class="is-active"
  data-ui-tab-disabled-class="is-disabled"
  data-ui-tab-hidden-attribute="data-hidden"
  data-ui-tab-use-hidden-attribute="false"
  data-ui-tab-orientation="vertical"
  data-ui-tab-activation="manual"
>
  ...
</div>

data-ui-tab-use-hidden-attribute="false" disables hidden attribute management. Any other value keeps it enabled.

Instance API

const result = init();
const instance = result.instances[0];

instance.activate('details');
instance.destroy();

Calling init() or autoInit() again for the same root returns the existing instance instead of binding duplicate event listeners.

Behavior

Initial tab priority:

  1. data-ui-tab-init
  2. Existing active class
  3. First enabled trigger

After initialization, the root element stores the active tab name in data-ui-tab-current.

Keyboard support:

With activation: 'manual' or data-ui-tab-activation="manual", arrow keys only move focus. Enter or Space activates the focused tab.

Events

Use on.init and on.change when initializing from JavaScript:

init({
  on: {
    init(detail) {
      console.log(detail.current);
    },
    change(detail) {
      console.log(detail.previous, detail.current);
    }
  }
});

The root element also dispatches ui-tab:init after the initial tab has been applied and ui-tab:change when the active tab changes after initialization.

const root = document.querySelector('[data-ui-tab]');

root.addEventListener('ui-tab:init', (event) => {
  console.log(event.detail.current);
});

root.addEventListener('ui-tab:change', (event) => {
  console.log(event.detail.previous, event.detail.current);
});

Event detail:

type UiTabEventDetail = {
  current: string;
  previous: string | null;
  trigger: HTMLElement;
  content: HTMLElement | null;
  instance: UiTabInstance;
};