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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@data-slot/tabs

v0.2.7

Published

Headless tabs component for vanilla JavaScript. Accessible, unstyled, tiny.

Readme

@data-slot/tabs

Headless tabs component for vanilla JavaScript. Accessible, unstyled, tiny.

Installation

npm install @data-slot/tabs

Quick Start

<div data-slot="tabs" data-default-value="one">
  <div data-slot="tabs-list">
    <button data-slot="tabs-trigger" data-value="one">Tab One</button>
    <button data-slot="tabs-trigger" data-value="two">Tab Two</button>
    <button data-slot="tabs-trigger" data-value="three">Tab Three</button>
    <div data-slot="tabs-indicator"></div>
  </div>
  <div data-slot="tabs-content" data-value="one">Content One</div>
  <div data-slot="tabs-content" data-value="two">Content Two</div>
  <div data-slot="tabs-content" data-value="three">Content Three</div>
</div>

<script type="module">
  import { create } from "@data-slot/tabs";
  
  const controllers = create();
</script>

API

create(scope?)

Auto-discover and bind all tabs instances in a scope (defaults to document).

import { create } from "@data-slot/tabs";

const controllers = create(); // Returns TabsController[]

createTabs(root, options?)

Create a controller for a specific element.

import { createTabs } from "@data-slot/tabs";

const tabs = createTabs(element, {
  defaultValue: "one",
  orientation: "horizontal",
  onValueChange: (value) => console.log(value),
});

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | defaultValue | string | First trigger's value | Initial selected tab | | orientation | "horizontal" \| "vertical" | "horizontal" | Tab orientation for keyboard nav | | activationMode | "auto" \| "manual" | "auto" | How tabs are activated with keyboard | | onValueChange | (value: string) => void | undefined | Callback when selected tab changes |

Data Attributes

Options can also be set via data attributes on the root element. JS options take precedence.

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | data-default-value | string | first tab | Initial selected tab | | data-orientation | string | "horizontal" | Tab orientation: horizontal, vertical | | data-activation-mode | string | "auto" | Activation mode: auto, manual |

<!-- Vertical tabs with manual activation -->
<div data-slot="tabs" data-orientation="vertical" data-activation-mode="manual">
  ...
</div>

Controller

| Method/Property | Description | |-----------------|-------------| | select(value) | Select a tab by value | | value | Currently selected value (readonly string) | | destroy() | Cleanup all event listeners |

Markup Structure

<div data-slot="tabs" data-default-value="initial-tab">
  <div data-slot="tabs-list">
    <button data-slot="tabs-trigger" data-value="unique-id">Label</button>
    <!-- Optional animated indicator -->
    <div data-slot="tabs-indicator"></div>
  </div>
  <div data-slot="tabs-content" data-value="unique-id">Panel content</div>
</div>

Optional Slots

  • tabs-indicator - Animated highlight that follows the selected tab

Styling

Basic Styling

/* Hidden panels */
[data-slot="tabs-content"][hidden] {
  display: none;
}

/* Active trigger */
[data-slot="tabs-trigger"][aria-selected="true"] {
  font-weight: bold;
  border-bottom: 2px solid currentColor;
}

/* Or use data-state */
[data-slot="tabs-trigger"][data-state="active"] {
  color: blue;
}

[data-slot="tabs-trigger"][data-state="inactive"] {
  color: gray;
}

Animated Indicator

The indicator receives CSS variables for positioning:

[data-slot="tabs-indicator"] {
  position: absolute;
  left: var(--active-tab-left);
  width: var(--active-tab-width);
  height: 2px;
  background: currentColor;
  transition: left 0.2s, width 0.2s;
}

/* Vertical orientation */
[data-slot="tabs-list"][aria-orientation="vertical"] [data-slot="tabs-indicator"] {
  top: var(--active-tab-top);
  height: var(--active-tab-height);
  width: 2px;
}

CSS Variables

| Variable | Description | |----------|-------------| | --active-tab-left | Left offset of active trigger | | --active-tab-width | Width of active trigger | | --active-tab-top | Top offset of active trigger | | --active-tab-height | Height of active trigger |

Tailwind Example

<div data-slot="tabs">
  <div data-slot="tabs-list" class="relative flex border-b">
    <button 
      data-slot="tabs-trigger" 
      data-value="one"
      class="px-4 py-2 aria-selected:text-blue-600"
    >
      Tab One
    </button>
    <div 
      data-slot="tabs-indicator" 
      class="absolute bottom-0 h-0.5 bg-blue-600 transition-all"
      style="left: var(--active-tab-left); width: var(--active-tab-width)"
    ></div>
  </div>
  <div data-slot="tabs-content" data-value="one" class="p-4">
    Content
  </div>
</div>

Keyboard Navigation

Horizontal Orientation

| Key | Action | |-----|--------| | ArrowLeft | Select previous tab | | ArrowRight | Select next tab | | Home | Select first tab | | End | Select last tab |

Vertical Orientation

| Key | Action | |-----|--------| | ArrowUp | Select previous tab | | ArrowDown | Select next tab | | Home | Select first tab | | End | Select last tab |

Accessibility

The component automatically handles:

  • role="tablist" on list
  • role="tab" on triggers
  • role="tabpanel" on content
  • aria-orientation on list
  • aria-selected on triggers
  • aria-controls linking triggers to panels
  • aria-labelledby linking panels to triggers
  • tabindex management (only selected tab is in tab order)

Events

Listen for changes via custom events:

element.addEventListener("tabs:change", (e) => {
  console.log("Selected tab:", e.detail.value);
});

License

MIT