Skip to content

Repository files navigation

data-slot logo

data-slot

Headless UI components for vanilla JavaScript. Tiny, accessible, unstyled.

Features

  • 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
  • Small bundles - Packages stay compact and tree-shake cleanly
  • Framework-agnostic - Works with vanilla JavaScript, no framework required
  • TypeScript - Full TypeScript support with type definitions included

Quick Start

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.

Installation

Install individual packages as needed:

# npm
npm install @data-slot/tabs @data-slot/dialog @data-slot/alert-dialog @data-slot/drawer

# pnpm
pnpm add @data-slot/tabs @data-slot/dialog @data-slot/alert-dialog @data-slot/drawer

# yarn
yarn add @data-slot/tabs @data-slot/dialog @data-slot/alert-dialog @data-slot/drawer

# bun
bun add @data-slot/tabs @data-slot/dialog @data-slot/alert-dialog @data-slot/drawer

Packages

All packages are independently installable. Each package includes its own README with detailed documentation.

For current sizes, run bun run check:sizes. This rebuilds all packages and reports minified and gzipped ESM entry sizes, sorted by gzip size. The measurements exclude imported dependencies, additional entry points, and type declarations; the ui entry is a re-export layer, not the full library bundle. The sizes below are a snapshot and may differ from the current build.

To measure an existing build without rebuilding, run bun run scripts/check-sizes.ts. The script discovers packages automatically and exits with an error if a build is missing.

Package Size Description Documentation
@data-slot/navigation-menu 7.2 KB Dropdown navigation menus README
@data-slot/core 5.5 KB Shared utilities README
@data-slot/command 4.7 KB Command palette with search README
@data-slot/hover-card 2.6 KB Hover/focus preview cards README
@data-slot/tabs 2.3 KB Tabbed interfaces, kbd nav README
@data-slot/radio-group 2.3 KB Single-select radios, form-ready README
@data-slot/tooltip 2.2 KB Hover/focus tooltips README
@data-slot/popover 2.0 KB Anchored floating content README
@data-slot/dialog 1.9 KB Modal dialogs, focus trap README
@data-slot/drawer 6.8 KB Swipeable drawers and sheets README
@data-slot/alert-dialog 1.8 KB Blocking confirmation dialogs README
@data-slot/collapsible 1.6 KB Simple show/hide toggle README
@data-slot/accordion 1.4 KB Collapsible sections README
@data-slot/toast 2.9 KB Imperative notifications README
@data-slot/carousel 1.8 KB Scroll-snap carousel README

API

All components follow the same pattern. You can either auto-discover all instances or create controllers for specific elements.

Auto-discovery

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"));

Manual creation

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 done

Other components

The same pattern applies to all components:

import { createDialog } from "@data-slot/dialog";
import { createAlertDialog } from "@data-slot/alert-dialog";
import { createDrawer } from "@data-slot/drawer";
import { createAccordion } from "@data-slot/accordion";
import { createPopover } from "@data-slot/popover";
import { createHoverCard } from "@data-slot/hover-card";
import { createCommand } from "@data-slot/command";

const dialog = createDialog(element);
const alertDialog = createAlertDialog(element);
const drawer = createDrawer(element);
const accordion = createAccordion(element);
const popover = createPopover(element);
const hoverCard = createHoverCard(element);
const command = createCommand(element);

Styling

Components are unstyled by default. Use data-state attributes and ARIA attributes for styling.

CSS

/* 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;
}

Tailwind CSS

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>

Examples

See live examples and component demos at data-slot.com.

Browser Support

data-slot uses ES modules and modern JavaScript features. It works in all modern browsers that support:

  • ES modules (<script type="module">)
  • querySelector and DOM APIs
  • Modern JavaScript (ES2017+)

For older browsers, use a bundler like Vite, Rollup, or Webpack with appropriate transpilation.

Development

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 build

# Build the documentation website
bun run build:website

# Preview the website in the Cloudflare Workers runtime
bun run preview:website

Each package has its own directory in packages/ with its own package.json, source code, and tests.

Documentation website

The Blume documentation lives in website and is the target of build:website and the Cloudflare deployment commands. Run it directly with bun run --cwd website dev after installing its dependencies and building the packages, or use the root commands below.

bun run install:docs
bun run dev:docs       # http://localhost:4322
bun run build:docs

Cloudflare Workers deployment

The documentation website is served from the data-slot Worker in the Bejamas OSS Cloudflare account (705e6a1ce1620c4ac2ce279a064dec41) at https://data-slot.com. The Worker custom domain is managed in wrangler.jsonc. A local deployment can be created with:

bun run deploy:website

For Cloudflare Workers Builds, connect bejamas/data-slot, leave the root directory blank (the repository root), and use main as the production branch. Use these commands:

  • Build command: bun run build:website
  • Deploy command: bunx wrangler deploy
  • Non-production branch deploy command: bunx wrangler versions upload

The build command installs the website's locked dependencies, builds the library packages, and outputs the site to website/dist, which Wrangler serves as static assets. PR builds upload preview versions; builds on main deploy to production.

Set the build variable BUN_VERSION to 1.3.14. Node.js is pinned to 24.18.0 in .node-version for Blume's Astro 7 runtime. If NODE_VERSION is set in the Cloudflare build settings, keep it aligned with that file. No application secrets or runtime variables are required.

License

MIT

About

vanilla JS behavior primitives for accessible components

Topics

Resources

Stars

65 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages