Headless UI components for vanilla JavaScript. Tiny, accessible, unstyled.
- Zero dependencies - No npm package dependencies, works everywhere
- Tree-shakeable - Import only what you use, keep bundles small
- Accessible - WAI-ARIA compliant with keyboard navigation built-in
- Tiny - Individual components range from 409 B to 2.7 KB
- Framework-agnostic - Works with vanilla JavaScript, no framework required
- TypeScript - Full TypeScript support with type definitions included
Add data-slot attributes to your HTML and initialize with JavaScript:
<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>
</div>
<div data-slot="tabs-content" data-value="one">Content One</div>
<div data-slot="tabs-content" data-value="two">Content Two</div>
</div>
<script type="module">
import { create } from "@data-slot/tabs";
const controllers = create();
controllers[0]?.select("two");
</script>The create() function auto-discovers all tabs in the DOM and binds them. Use the controller to programmatically control the component.
Install individual packages as needed:
# npm
npm install @data-slot/tabs @data-slot/dialog
# pnpm
pnpm add @data-slot/tabs @data-slot/dialog
# yarn
yarn add @data-slot/tabs @data-slot/dialog
# bun
bun add @data-slot/tabs @data-slot/dialogAll packages are independently installable. Each package includes its own README with detailed documentation.
| Package | Size | Description | Documentation |
|---|---|---|---|
@data-slot/navigation-menu |
2.7 KB | Dropdown navigation menus | README |
@data-slot/tabs |
1.7 KB | Tabbed interfaces, kbd nav | README |
@data-slot/dialog |
1.4 KB | Modal dialogs, focus trap | README |
@data-slot/accordion |
1.2 KB | Collapsible sections | README |
@data-slot/tooltip |
821 B | Hover/focus tooltips | README |
@data-slot/popover |
806 B | Anchored floating content | README |
@data-slot/disclosure |
629 B | Simple show/hide toggle | README |
@data-slot/core |
409 B | Shared utilities | README |
All components follow the same pattern. You can either auto-discover all instances or create controllers for specific elements.
The create() function finds all component instances in the DOM (or within a scope):
import { create } from "@data-slot/tabs";
// Find all tabs in the document
const controllers = create(); // Returns TabsController[]
// Or scope to a specific element
const controllers = create(document.querySelector(".my-app"));Create a controller for a specific element with options:
import { createTabs } from "@data-slot/tabs";
const tabs = createTabs(document.querySelector('[data-slot="tabs"]'), {
defaultValue: "news",
onValueChange: (value) => console.log("Selected:", value),
});
tabs.select("sports"); // Programmatic control
tabs.destroy(); // Cleanup when doneThe same pattern applies to all components:
import { createDialog } from "@data-slot/dialog";
import { createAccordion } from "@data-slot/accordion";
import { createPopover } from "@data-slot/popover";
const dialog = createDialog(element);
const accordion = createAccordion(element);
const popover = createPopover(element);Components are unstyled by default. Use data-state attributes and ARIA attributes for styling.
/* Active tab trigger */
[data-slot="tabs-trigger"][aria-selected="true"] {
font-weight: bold;
border-bottom: 2px solid currentColor;
}
/* Using data-state */
[data-slot="tabs-trigger"][data-state="active"] {
color: blue;
}
[data-slot="tabs-trigger"][data-state="inactive"] {
color: gray;
}
/* Dialog overlay */
[data-slot="dialog"][data-state="open"] [data-slot="dialog-overlay"] {
background: rgba(0, 0, 0, 0.5);
}
/* Accordion content */
[data-slot="accordion-content"][hidden] {
display: none;
}Use Tailwind's aria-* and data-* variants:
<button
data-slot="tabs-trigger"
class="px-4 py-2 aria-selected:font-bold aria-selected:border-b-2 aria-selected:text-blue-600"
>
Tab
</button>
<div
data-slot="dialog-content"
class="data-[state=open]:flex data-[state=closed]:hidden"
>
Dialog content
</div>See live examples and component demos at data-slot.com.
data-slot uses ES modules and modern JavaScript features. It works in all modern browsers that support:
- ES modules (
<script type="module">) querySelectorand DOM APIs- Modern JavaScript (ES2017+)
For older browsers, use a bundler like Vite, Rollup, or Webpack with appropriate transpilation.
This is a monorepo managed with Bun workspaces. Each package is independently buildable and testable.
# Install dependencies
bun install
# Run tests
bun test
# Type check
bun run typecheck
# Build all packages
bun run buildEach package has its own directory in packages/ with its own package.json, source code, and tests.
MIT