Skip to content
data-slotv1.0.0
Esc
↑↓navigate↵open⌘Jpreview
On this page

Quick start

headless UI components for vanilla JavaScript.

tiny · accessible · unstyled

data-slot adds accessible behavior to your markup with a few attributes and a small JavaScript module. Bring your own styles, use your favorite framework, or use none at all.

Install a component

Install only the packages you need. Each component is available separately.

bun add @data-slot/tabs
npm install @data-slot/tabs
pnpm add @data-slot/tabs

Assemble the markup

Use data-slot to identify each part. Related triggers and panels share a data-value.

<div data-slot="tabs" data-default-value="one">
  <div data-slot="tabs-list" aria-label="Getting started">
    <button data-slot="tabs-trigger" data-value="one">Overview</button>
    <button data-slot="tabs-trigger" data-value="two">Details</button>
  </div>
  <div data-slot="tabs-content" data-value="one">Your overview.</div>
  <div data-slot="tabs-content" data-value="two">Your details.</div>
</div>

Add the behavior

Import and call create() to make the tabs interactive:

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

// Find and initialize all [data-slot="tabs"] elements.
create();

The tabs now respond to clicks and keyboard navigation.

To target a specific element and control its tabs from JavaScript, use createTabs() instead:

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

const element = document.querySelector('[data-slot="tabs"]');
const tabs = createTabs(element);

tabs.select('two'); // Show the Details panel.

Call tabs.destroy() before removing that element to clean up its listeners.

Run either setup after the markup is in the document. Use a bundler such as Vite to resolve package imports, and use type="module" if you add the code in a script tag.

Make it yours

Components provide behavior and accessibility attributes. Styling is yours. Every example includes CSS and Tailwind versions, plus the initialization code.

Preview
<div data-slot="tabs">
  <div data-slot="tabs-list">
    <button data-slot="tabs-trigger" data-value="one">Tab</button>
  </div>
  <div data-slot="tabs-content" data-value="one">...</div>
</div>
/* Use data-state for styling */
[data-slot="tabs-trigger"][data-state="active"] {
  font-weight: bold;
  border-bottom: 2px solid;
}
import { create } from "@data-slot/tabs";

const [tabs] = create();
tabs.select("css");
console.log(tabs.value); // "css"
Show codeHide code
<div data-slot="tabs" data-default-value="one">
  <div data-slot="tabs-list" class="tabs-list">
    <button data-slot="tabs-trigger" data-value="one" class="tabs-trigger">Tab One</button>
    <button data-slot="tabs-trigger" data-value="two" class="tabs-trigger">Tab Two</button>
  </div>
  <div data-slot="tabs-content" data-value="one" class="tabs-content">Content One</div>
  <div data-slot="tabs-content" data-value="two" class="tabs-content">Content Two</div>
</div>

<style>
  .tabs-list {
    display: flex;
    border-bottom: 1px solid var(--border);
    margin-bottom: 1rem;
  }
  .tabs-trigger {
    padding: 0.5rem 1rem;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    color: var(--muted);
    margin-bottom: 0;
  }
  .tabs-trigger[data-state="active"] {
    color: var(--text);
    border-bottom-color: var(--text);
    font-weight: 500;
  }
  .tabs-content { padding: 1rem 0; }
  .tabs-content[hidden] { display: none; }
</style>
<div data-slot="tabs" data-default-value="one">
  <div data-slot="tabs-list" class="flex border-b border-[var(--border)] mb-4">
    <button
      data-slot="tabs-trigger"
      data-value="one"
      class="px-4 py-2 border-b-2 border-transparent text-[var(--muted)] cursor-pointer
             data-[state=active]:text-text data-[state=active]:border-text
             data-[state=active]:font-medium"
    >
      Tab One
    </button>
    <button
      data-slot="tabs-trigger"
      data-value="two"
      class="px-4 py-2 border-b-2 border-transparent text-[var(--muted)] cursor-pointer
             data-[state=active]:text-text data-[state=active]:border-text
             data-[state=active]:font-medium"
    >
      Tab Two
    </button>
  </div>
  <div data-slot="tabs-content" data-value="one" class="py-4 hidden data-[state=active]:block">
    Content One
  </div>
  <div data-slot="tabs-content" data-value="two" class="py-4 hidden data-[state=active]:block">
    Content Two
  </div>
</div>

Tailwind examples use Tailwind CSS v4. Examples with animation utilities also use tw-animate-css. See the styling guide for setup.

Explore the components

Pick a component in the sidebar. Every page follows the same structure: Anatomy → Examples → API reference.