settings
examples mode:
/
ver: 0.2.163
github · npm
data-slot

headless UI components for vanilla JavaScript.

tiny · accessible · unstyled
~~~

PACKAGES

+----------------------------+---------+--------------------------------+
| Package                    |    Size | Description                    |
|----------------------------+---------+--------------------------------|
| @data-slot/navigation-menu | 10.3 KB | Dropdown navigation menus      |
| @data-slot/core            |  5.8 KB | Shared utilities               |
| @data-slot/command         |  4.7 KB | Command palette, ranked search |
| @data-slot/select          |  4.5 KB | Dropdown select, form-ready    |
| @data-slot/combobox        |  4.5 KB | Autocomplete input             |
| @data-slot/accordion       |  3.5 KB | Collapsible sections           |
| @data-slot/tooltip         |  2.7 KB | Hover/focus tooltips           |
| @data-slot/dropdown-menu   |  2.7 KB | Action menus, kbd nav          |
| @data-slot/slider          |  2.7 KB | Single/range value sliders     |
| @data-slot/tabs            |  2.3 KB | Tabbed interfaces, kbd nav     |
| @data-slot/popover         |  2.0 KB | Anchored floating content      |
| @data-slot/dialog          |  1.9 KB | Modal dialogs, focus trap      |
| @data-slot/switch          |  1.8 KB | Form-ready on/off switch       |
| @data-slot/alert-dialog    |  1.8 KB | Blocking confirmation dialogs  |
| @data-slot/collapsible     |  1.6 KB | Simple show/hide toggle        |
+----------------------------+---------+--------------------------------+

INSTALLATION

Install only what you need:

bun add @data-slot/tabs
bun add @data-slot/dialog

QUICK START

Components use data-slot attributes for markup:

<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>

Import and call create() to bind behavior:

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

// Auto-discover and bind all [data-slot="tabs"] elements
const controllers = create();

// Or target a specific element
import { createTabs } from "@data-slot/tabs";
const tabs = createTabs(element);

tabs.select("two");   // programmatic control
tabs.destroy();       // cleanup
~~~

LIVE DEMOS

Interactive examples using the library. Inspect the source for markup patterns.

Tabs

↓ interactive
<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"
View source
<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 #ccc;
    margin-bottom: 1rem;
  }
  .tabs-trigger {
    padding: 0.5rem 1rem;
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    color: #666;
  }
  .tabs-trigger[data-state="active"] {
    color: #1a1a1a;
    border-bottom-color: #1a1a1a;
    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-gray-300 mb-4">
    <button
      data-slot="tabs-trigger"
      data-value="one"
      class="px-4 py-2 border-b-2 border-transparent text-gray-500 cursor-pointer
             data-[state=active]:text-gray-900 data-[state=active]:border-gray-900
             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-gray-500 cursor-pointer
             data-[state=active]:text-gray-900 data-[state=active]:border-gray-900
             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>
↓ with animated indicator
The indicator slides smoothly to the active tab using CSS transitions. Position is set via CSS variables: --active-tab-left and --active-tab-width.
Zero JavaScript for animation — just CSS transition on transform and width. Works with keyboard navigation too.
Add data-slot="tabs-indicator" inside your tabs-list. The component sets CSS variables automatically.
View source
<div data-slot="tabs" data-default-value="overview">
  <div data-slot="tabs-list" class="tabs-list-indicator">
    <div data-slot="tabs-indicator" class="tabs-indicator"></div>
    <button data-slot="tabs-trigger" data-value="overview" class="tabs-trigger">Overview</button>
    <button data-slot="tabs-trigger" data-value="features" class="tabs-trigger">Features</button>
    <button data-slot="tabs-trigger" data-value="api" class="tabs-trigger">API</button>
  </div>
  <div data-slot="tabs-content" data-value="overview">Content</div>
</div>

<style>
  .tabs-list-indicator {
    position: relative;
    display: flex;
    gap: 0.25rem;
    padding: 0.25rem;
    background: #f0eeeb;
    border-radius: 6px;
    overflow: auto;
  }
  .tabs-indicator {
    position: absolute;
    top: 0.25rem;
    height: calc(100% - 0.5rem);
    background: #fff;
    border-radius: 4px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.12);
    transform: translateX(var(--active-tab-left, 0));
    width: var(--active-tab-width, 0);
    transition: transform 0.2s, width 0.2s;
  }
  .tabs-trigger {
    position: relative;
    z-index: 1;
    padding: 0.375rem 0.75rem;
    background: none;
    border: none;
    cursor: pointer;
  }
</style>
<div data-slot="tabs" data-default-value="overview">
  <div
    data-slot="tabs-list"
    class="relative flex gap-1 p-1 bg-code-bg rounded-md mb-4"
  >
    <div
      data-slot="tabs-indicator"
      class="absolute top-1 h-[calc(100%-0.5rem)] bg-white rounded shadow
             transition-all duration-200
             [transform:translateX(var(--active-tab-left,0))]
             [width:var(--active-tab-width,0)]"
    ></div>
    <button
      data-slot="tabs-trigger"
      data-value="overview"
      class="relative z-10 px-3 py-1.5 bg-transparent border-none cursor-pointer"
    >
      Overview
    </button>
    <button
      data-slot="tabs-trigger"
      data-value="features"
      class="relative z-10 px-3 py-1.5 bg-transparent border-none cursor-pointer"
    >
      Features
    </button>
    <button
      data-slot="tabs-trigger"
      data-value="api"
      class="relative z-10 px-3 py-1.5 bg-transparent border-none cursor-pointer"
    >
      API
    </button>
  </div>
  <div data-slot="tabs-content" data-value="overview">Content</div>
</div>

Accordion

↓ single mode (default)
Unlike React-based headless libraries, data-slot works with vanilla HTML. Just add data-slot attributes to your markup and call create(). No framework, no virtual DOM, no build step required.
Yes. All components implement WAI-ARIA patterns with proper roles, keyboard navigation, and focus management out of the box.
No dependencies. Each component is standalone and tree-shakeable.
Unlike React-based headless libraries, data-slot works with vanilla HTML. Just add data-slot attributes to your markup and call create(). No framework, no virtual DOM, no build step required.
Yes. All components implement WAI-ARIA patterns with proper roles, keyboard navigation, and focus management out of the box.
No dependencies. Each component is standalone and tree-shakeable.
View source
<div data-slot="accordion" data-default-value="features">
  <div data-slot="accordion-item" data-value="features" class="accordion-item">
    <button data-slot="accordion-trigger" class="accordion-trigger">
      <span>What makes data-slot different?</span>
      <span data-slot="accordion-trigger-icon" class="accordion-trigger-icon">+</span>
    </button>
    <div data-slot="accordion-content" class="accordion-content">
      <div data-slot="accordion-content-inner" class="accordion-content-inner">
        Unlike React-based headless libraries, data-slot works with vanilla HTML.
      </div>
    </div>
  </div>
  <div data-slot="accordion-item" data-value="accessibility" class="accordion-item">
    <button data-slot="accordion-trigger" class="accordion-trigger">
      <span>Is it accessible?</span>
      <span data-slot="accordion-trigger-icon" class="accordion-trigger-icon">+</span>
    </button>
    <div data-slot="accordion-content" class="accordion-content">
      <div data-slot="accordion-content-inner" class="accordion-content-inner">
        Yes. All components implement WAI-ARIA patterns.
      </div>
    </div>
  </div>
</div>

<style>
  .accordion-item { border-bottom: 1px dashed #ccc; }
  .accordion-item:last-child { border-bottom: none; }
  .accordion-trigger {
    width: 100%;
    padding: 0.75rem 0;
    background: none;
    border: none;
    text-align: left;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 1rem;
  }
  .accordion-trigger-icon {
    margin-left: auto;
    color: #666;
    transition: transform 0.25s ease;
  }
  .accordion-item[data-state="open"] .accordion-trigger-icon {
    transform: rotate(45deg);
  }
  .accordion-content {
    overflow: hidden;
    height: var(--accordion-panel-height);
    transition: height 0.3s ease-out;
  }
  .accordion-content[data-starting-style] {
    height: 0;
  }
  .accordion-content-inner { padding: 0 0 1rem; color: #666; }
</style>
<div data-slot="accordion" data-default-value="features">
  <div
    data-slot="accordion-item"
    data-value="features"
    class="group border-b border-dashed border-gray-300 last:border-b-0"
  >
    <button
      data-slot="accordion-trigger"
      class="flex w-full items-center gap-4 bg-transparent py-3 text-left"
    >
      <span>What makes data-slot different?</span>
      <span
        data-slot="accordion-trigger-icon"
        class="ml-auto shrink-0 text-gray-500 transition-transform group-data-[state=open]:rotate-45"
      >
        +
      </span>
    </button>
    <div
      data-slot="accordion-content"
      class="overflow-hidden text-sm text-gray-500 data-open:animate-accordion-down data-closed:animate-accordion-up"
    >
      <div
        data-slot="accordion-content-inner"
        class="h-(--accordion-panel-height) pb-4 pt-0 data-ending-style:h-0 data-starting-style:h-0"
      >
        Unlike React-based headless libraries, data-slot works with vanilla HTML.
      </div>
    </div>
  </div>
  <div
    data-slot="accordion-item"
    data-value="accessibility"
    class="group border-b border-dashed border-gray-300 last:border-b-0"
  >
    <button
      data-slot="accordion-trigger"
      class="flex w-full items-center gap-4 bg-transparent py-3 text-left"
    >
      <span>Is it accessible?</span>
      <span
        data-slot="accordion-trigger-icon"
        class="ml-auto shrink-0 text-gray-500 transition-transform group-data-[state=open]:rotate-45"
      >
        +
      </span>
    </button>
    <div
      data-slot="accordion-content"
      class="overflow-hidden text-sm text-gray-500 data-open:animate-accordion-down data-closed:animate-accordion-up"
    >
      <div
        data-slot="accordion-content-inner"
        class="h-(--accordion-panel-height) pb-4 pt-0 data-ending-style:h-0 data-starting-style:h-0"
      >
        Yes. All components implement WAI-ARIA patterns.
      </div>
    </div>
  </div>
</div>
↓ multiple mode
With multiple: true, multiple items can be open simultaneously.
Try clicking both items — they stay open independently.
With multiple: true, multiple items can be open simultaneously.
Try clicking both items — they stay open independently.
View source
<div data-slot="accordion">
  <div data-slot="accordion-item" data-value="item1" class="accordion-item">
    <button data-slot="accordion-trigger" class="accordion-trigger">
      <span>First item</span>
      <span data-slot="accordion-trigger-icon" class="accordion-trigger-icon">+</span>
    </button>
    <div data-slot="accordion-content" class="accordion-content">
      <div data-slot="accordion-content-inner" class="accordion-content-inner">
        With <code>multiple: true</code>, multiple items can be open simultaneously.
      </div>
    </div>
  </div>
  <div data-slot="accordion-item" data-value="item2" class="accordion-item">
    <button data-slot="accordion-trigger" class="accordion-trigger">
      <span>Second item</span>
      <span data-slot="accordion-trigger-icon" class="accordion-trigger-icon">+</span>
    </button>
    <div data-slot="accordion-content" class="accordion-content">
      <div data-slot="accordion-content-inner" class="accordion-content-inner">
        Try clicking both items — they stay open independently.
      </div>
    </div>
  </div>
</div>

<script type="module">
  import { createAccordion } from '@data-slot/accordion';
  createAccordion(element, { multiple: true });
</script>
<div data-slot="accordion">
  <div
    data-slot="accordion-item"
    data-value="item1"
    class="group border-b border-dashed border-gray-300 last:border-b-0"
  >
    <button
      data-slot="accordion-trigger"
      class="flex w-full items-center gap-4 bg-transparent py-3 text-left"
    >
      <span>First item</span>
      <span
        data-slot="accordion-trigger-icon"
        class="ml-auto shrink-0 text-gray-500 transition-transform group-data-[state=open]:rotate-45"
      >
        +
      </span>
    </button>
    <div
      data-slot="accordion-content"
      class="overflow-hidden text-sm text-gray-500 data-open:animate-accordion-down data-closed:animate-accordion-up"
    >
      <div
        data-slot="accordion-content-inner"
        class="h-(--accordion-panel-height) pb-4 pt-0 data-ending-style:h-0 data-starting-style:h-0"
      >
        With <code>multiple: true</code>, multiple items can be open simultaneously.
      </div>
    </div>
  </div>
  <div
    data-slot="accordion-item"
    data-value="item2"
    class="group border-b border-dashed border-gray-300 last:border-b-0"
  >
    <button
      data-slot="accordion-trigger"
      class="flex w-full items-center gap-4 bg-transparent py-3 text-left"
    >
      <span>Second item</span>
      <span
        data-slot="accordion-trigger-icon"
        class="ml-auto shrink-0 text-gray-500 transition-transform group-data-[state=open]:rotate-45"
      >
        +
      </span>
    </button>
    <div
      data-slot="accordion-content"
      class="overflow-hidden text-sm text-gray-500 data-open:animate-accordion-down data-closed:animate-accordion-up"
    >
      <div
        data-slot="accordion-content-inner"
        class="h-(--accordion-panel-height) pb-4 pt-0 data-ending-style:h-0 data-starting-style:h-0"
      >
        Try clicking both items — they stay open independently.
      </div>
    </div>
  </div>
</div>

<script type="module">
  import { createAccordion } from '@data-slot/accordion';
  createAccordion(element, { multiple: true });
</script>

Dialog

↓ interactive
View source
<div data-slot="dialog">
  <button data-slot="dialog-trigger" class="dialog-trigger-btn">Open Dialog</button>
  <div data-slot="dialog-overlay" class="dialog-overlay" hidden></div>
  <div data-slot="dialog-content" class="dialog-panel" hidden>
    <h2 data-slot="dialog-title" class="dialog-title">Confirm Action</h2>
    <p data-slot="dialog-description" class="dialog-description">
      This dialog traps focus and can be closed with Escape or clicking outside.
    </p>
    <button data-slot="dialog-close" class="dialog-close-btn">Close</button>
  </div>
</div>

<style>
  .dialog-trigger-btn {
    padding: 0.5rem 1rem;
    background: #1a1a1a;
    color: #faf9f7;
    border: none;
    cursor: pointer;
  }
  .dialog-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.5);
    z-index: 100;
    opacity: 0;
    transition: opacity 0.2s ease;
  }
  .dialog-overlay[data-open] { opacity: 1; }
  .dialog-overlay[data-starting-style],
  .dialog-overlay[data-ending-style] { opacity: 0; }
  .dialog-overlay[hidden] { display: none; }
  .dialog-panel {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.95);
    background: #faf9f7;
    padding: 2rem;
    max-width: 400px;
    width: calc(100% - 2rem);
    border: 1px solid #ccc;
    z-index: 101;
    opacity: 0;
    transition:
      opacity 0.2s ease,
      transform 0.2s ease;
  }
  .dialog-panel[data-open] {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
  .dialog-panel[data-starting-style],
  .dialog-panel[data-ending-style] {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.95);
  }
  .dialog-panel[hidden] { display: none; }
  .dialog-title { font-weight: 700; margin-bottom: 0.5rem; }
  .dialog-description { color: #666; margin-bottom: 1.5rem; }
  .dialog-close-btn {
    padding: 0.4rem 0.8rem;
    background: none;
    border: 1px solid #ccc;
    cursor: pointer;
  }
</style>
<div data-slot="dialog" class="group/dialog">
  <button
    data-slot="dialog-trigger"
    class="px-4 py-2 bg-gray-900 text-white border-none cursor-pointer
           hover:opacity-90 transition-opacity"
  >
    Open Dialog
  </button>
  <div
    data-slot="dialog-overlay"
    class="fixed inset-0 bg-black/50 z-50 opacity-0 transition-opacity duration-200
           data-[open]:opacity-100
           data-[starting-style]:opacity-0
           data-[ending-style]:opacity-0"
    hidden
  ></div>
  <div
    data-slot="dialog-content"
    class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
           bg-white p-8 max-w-md w-[calc(100%-2rem)] border border-gray-300 rounded-lg z-50
           opacity-0 scale-95 transition-all duration-200
           data-[open]:opacity-100
           data-[open]:scale-100
           data-[starting-style]:opacity-0
           data-[starting-style]:scale-95
           data-[ending-style]:opacity-0
           data-[ending-style]:scale-95"
    hidden
  >
    <h2 data-slot="dialog-title" class="font-bold mb-2 mt-0">Confirm Action</h2>
    <p data-slot="dialog-description" class="text-gray-500 mb-6">
      This dialog traps focus and can be closed with Escape or clicking outside.
    </p>
    <button
      data-slot="dialog-close"
      class="px-3 py-1.5 bg-transparent border border-gray-300 cursor-pointer
             hover:bg-code-bg transition-colors"
    >
      Close
    </button>
  </div>
</div>

Alert Dialog

↓ interactive

alert-dialog-action is just a styled slot. This demo closes after confirm with custom JavaScript.

View source
<div data-slot="alert-dialog" class="alert-dialog-root">
  <button data-slot="alert-dialog-trigger" class="alert-dialog-trigger">
    Delete Project
  </button>

  <div data-slot="alert-dialog-portal">
    <div data-slot="alert-dialog-overlay" class="alert-dialog-overlay" hidden></div>
    <div data-slot="alert-dialog-content" class="alert-dialog-panel" hidden>
      <div data-slot="alert-dialog-header" class="alert-dialog-header">
        <div data-slot="alert-dialog-media" class="alert-dialog-media">!</div>
        <h2 data-slot="alert-dialog-title" class="alert-dialog-title">Delete project?</h2>
        <p data-slot="alert-dialog-description" class="alert-dialog-description">
          This will permanently remove your production config and API keys.
        </p>
      </div>

      <div data-slot="alert-dialog-footer" class="alert-dialog-footer">
        <button data-slot="alert-dialog-cancel" class="alert-dialog-cancel">Cancel</button>
        <button
          data-slot="alert-dialog-action"
          data-demo-alert-confirm
          class="alert-dialog-action"
        >
          Delete
        </button>
      </div>
    </div>
  </div>
</div>

<script type="module">
  import { createAlertDialog } from "@data-slot/alert-dialog";

  const root = document.querySelector('[data-slot="alert-dialog"]');
  const alertDialog = createAlertDialog(root);

  root.querySelector('[data-demo-alert-confirm]')?.addEventListener("click", () => {
    // Run the destructive action, then close explicitly.
    alertDialog.close();
  });
</script>

<style>
  .alert-dialog-trigger {
    padding: 0.5rem 1rem;
    background: #1a1a1a;
    color: #faf9f7;
    border: none;
    cursor: pointer;
  }
  .alert-dialog-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 100;
    opacity: 0;
    transition: opacity 0.2s ease;
  }
  .alert-dialog-overlay[data-open] { opacity: 1; }
  .alert-dialog-overlay[data-starting-style],
  .alert-dialog-overlay[data-ending-style] { opacity: 0; }
  .alert-dialog-panel {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.95);
    background: #faf9f7;
    padding: 2rem;
    max-width: 400px;
    width: calc(100% - 2rem);
    border: 1px solid #ccc;
    border-radius: 0.5rem;
    z-index: 101;
    opacity: 0;
    transition:
      opacity 0.2s ease,
      transform 0.2s ease;
  }
  .alert-dialog-panel[data-open] {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
  .alert-dialog-panel[data-starting-style],
  .alert-dialog-panel[data-ending-style] {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.95);
  }
  .alert-dialog-header {
    display: grid;
    justify-items: center;
    gap: 0.5rem;
    text-align: center;
    margin-bottom: 1.5rem;
  }
  .alert-dialog-media {
    width: 2rem;
    height: 2rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: #f0eeeb;
    border: 1px solid #ccc;
    color: #1a1a1a;
    font-weight: 700;
  }
  .alert-dialog-title {
    margin: 0;
    font-weight: 700;
  }
  .alert-dialog-description {
    margin: 0;
    color: #666;
    line-height: 1.5;
  }
  .alert-dialog-footer {
    display: flex;
    flex-direction: column-reverse;
    gap: 0.5rem;
  }
  .alert-dialog-cancel,
  .alert-dialog-action {
    padding: 0.4rem 0.8rem;
    cursor: pointer;
  }
  .alert-dialog-cancel {
    background: none;
    border: 1px solid #ccc;
  }
  .alert-dialog-action {
    background: #1a1a1a;
    color: #faf9f7;
    border: none;
  }
  @media (min-width: 640px) {
    .alert-dialog-footer {
      flex-direction: row;
      justify-content: flex-end;
    }
  }
</style>
<div data-slot="alert-dialog">
  <button
    data-slot="alert-dialog-trigger"
    class="px-4 py-2 bg-gray-900 text-white border-none cursor-pointer
           hover:opacity-90 transition-opacity"
  >
    Delete Project
  </button>

  <div data-slot="alert-dialog-portal">
    <div
      data-slot="alert-dialog-overlay"
      class="fixed inset-0 bg-black/50 z-50 opacity-0 transition-opacity duration-200
             data-[open]:opacity-100
             data-[starting-style]:opacity-0
             data-[ending-style]:opacity-0"
      hidden
    ></div>
    <div
      data-slot="alert-dialog-content"
      class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
             bg-white p-8 max-w-md w-[calc(100%-2rem)] border border-gray-300 rounded-lg z-50
             opacity-0 scale-95 transition-all duration-200
             data-[open]:opacity-100
             data-[open]:scale-100
             data-[starting-style]:opacity-0
             data-[starting-style]:scale-95
             data-[ending-style]:opacity-0
             data-[ending-style]:scale-95"
      hidden
    >
      <div
        data-slot="alert-dialog-header"
        class="grid justify-items-center gap-2 text-center mb-6"
      >
        <div
          data-slot="alert-dialog-media"
          class="inline-flex size-8 items-center justify-center rounded-full border border-gray-300 bg-[#f0eeeb] font-bold text-[#1a1a1a]"
        >
          !
        </div>
        <h2 data-slot="alert-dialog-title" class="font-bold mt-0 mb-0">
          Delete project?
        </h2>
        <p data-slot="alert-dialog-description" class="text-gray-500 m-0">
          This will permanently remove your production config and API keys.
        </p>
      </div>

      <div
        data-slot="alert-dialog-footer"
        class="flex flex-col-reverse gap-2 sm:flex-row sm:justify-end"
      >
        <button
          data-slot="alert-dialog-cancel"
          class="px-3 py-1.5 bg-transparent border border-gray-300 cursor-pointer hover:bg-code-bg transition-colors"
        >
          Cancel
        </button>
        <button
          data-slot="alert-dialog-action"
          data-demo-alert-confirm
          class="px-3 py-1.5 bg-gray-900 text-white border-none cursor-pointer hover:opacity-90 transition-opacity"
        >
          Delete
        </button>
      </div>
    </div>
  </div>
</div>

Collapsible

↓ interactive

Collapsible is the simplest component — just a trigger and content.

Perfect for FAQ items, collapsible sections, or any show/hide pattern.

Collapsible is the simplest component — just a trigger and content.

Perfect for FAQ items, collapsible sections, or any show/hide pattern.

View source
<div data-slot="collapsible">
  <button data-slot="collapsible-trigger" class="collapsible-trigger-btn">
    Show more details
  </button>
  <div class="collapsible-content-wrapper">
    <div data-slot="collapsible-content" class="collapsible-content">
      <div class="collapsible-content-inner">
        <p>Collapsible is the simplest component — just a trigger and content.</p>
        <p>Perfect for FAQ items, collapsible sections, or any show/hide pattern.</p>
      </div>
    </div>
  </div>
</div>

<style>
  .collapsible-trigger-btn {
    padding: 0.5rem 1rem;
    background: none;
    border: 1px dashed #ccc;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.5rem;
  }
  .collapsible-trigger-btn::before {
    content: "";
    font-size: 0.7rem;
    transition: transform 0.2s;
  }
  .collapsible-trigger-btn[aria-expanded="true"]::before {
    transform: rotate(90deg);
  }
  .collapsible-content-wrapper {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.3s;
  }
  [data-slot="collapsible"][data-state="open"] .collapsible-content-wrapper {
    grid-template-rows: 1fr;
  }
  .collapsible-content {
    overflow: hidden;
    min-height: 0;
  }
  .collapsible-content[hidden] { display: block !important; }
  .collapsible-content-inner {
    padding: 1rem;
    margin-top: 0.5rem;
    background: #f0eeeb;
  }
</style>
<div data-slot="collapsible" class="group">
  <button
    data-slot="collapsible-trigger"
    class="px-4 py-2 bg-transparent border border-dashed border-gray-300
           cursor-pointer flex items-center gap-2 hover:bg-code-bg
           before:content-['▶'] before:text-[0.7rem]
           before:transition-transform before:duration-200
           aria-expanded:before:rotate-90"
  >
    Show more details
  </button>
  <div
    class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-300
           group-data-[state=open]:grid-rows-[1fr]"
  >
    <div data-slot="collapsible-content" class="overflow-hidden min-h-0">
      <div class="p-4 mt-2 bg-code-bg">
        <p>Collapsible is the simplest component — just a trigger and content.</p>
        <p class="mt-2">Perfect for FAQ items, collapsible sections, or any show/hide pattern.</p>
      </div>
    </div>
  </div>
</div>

Tooltip

↓ Base/shadcn composition + logical sides
Side: top (default)
Side: bottom
Side: left
Side: right
Side: inline-start
Side: inline-end
Side: top (default)
Side: bottom
Side: left
Side: right
Side: inline-start
Side: inline-end

Use a real tooltip-arrow element for Base/shadcn composition. The runtime sets arrow top / left inline, so authored CSS should only handle edge attachment, rotation, and any optional left/right nudge.

View source
<div data-slot="tooltip" class="tooltip-root">
  <button data-slot="tooltip-trigger" class="tooltip-trigger-btn">Hover me</button>
  <div
    data-slot="tooltip-content"
    data-side="top"
    class="tooltip-content tooltip-content-logical"
  >
    Tooltip content
    <div
      data-slot="tooltip-arrow"
      class="tooltip-arrow"
    ></div>
  </div>
</div>

<style>
  .tooltip-root { display: inline-block; }
  .tooltip-trigger-btn {
    padding: 0.5rem 0.9rem;
    background: #fff;
    border: 1px dashed #d1d5db;
    border-radius: 999px;
    cursor: help;
    color: #111827;
  }
  .tooltip-content {
    position: absolute;
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    background: #111827;
    color: #f9fafb;
    padding: 0.375rem 0.75rem;
    border-radius: 1rem;
    font-size: 0.75rem;
    white-space: nowrap;
    z-index: 50;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform-origin: var(--transform-origin, center);
    --tooltip-slide-x: 0px;
    --tooltip-slide-y: 0px;
    transition: opacity 0.15s, visibility 0s linear 0.15s;
  }
  .tooltip-content[data-side="top"] { --tooltip-slide-y: 8px; }
  .tooltip-content[data-side="bottom"] { --tooltip-slide-y: -8px; }
  .tooltip-content[data-side="left"] { --tooltip-slide-x: 8px; }
  .tooltip-content[data-side="right"] { --tooltip-slide-x: -8px; }
  .tooltip-content-logical[data-side="inline-start"] { --tooltip-slide-x: 8px; }
  .tooltip-content-logical[data-side="inline-end"] { --tooltip-slide-x: -8px; }
  .tooltip-content[data-open] {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition-delay: 0s;
    animation: tooltip-in 140ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  .tooltip-content[data-closed] {
    pointer-events: none;
    visibility: visible;
    animation: tooltip-out 100ms ease-in forwards;
  }
  .tooltip-content[data-instant] {
    transition: none;
    animation: none;
  }
  .tooltip-arrow {
    position: absolute;
    width: 0.625rem;
    height: 0.625rem;
    background: inherit;
    border-radius: 2px;
    transform: rotate(45deg);
  }
  .tooltip-arrow[data-side="top"] { bottom: -0.25rem; }
  .tooltip-arrow[data-side="bottom"] { top: -0.25rem; }
  .tooltip-arrow[data-side="left"] {
    right: -0.25rem;
    transform: translateX(-1.5px) rotate(45deg);
  }
  .tooltip-arrow[data-side="right"] {
    left: -0.25rem;
    transform: translateX(1.5px) rotate(45deg);
  }
  .tooltip-arrow[data-side="inline-start"] {
    right: -0.25rem;
    transform: translateX(-1.5px) rotate(45deg);
  }
  .tooltip-arrow[data-side="inline-end"] {
    left: -0.25rem;
    transform: translateX(1.5px) rotate(45deg);
  }
  @keyframes tooltip-in {
    from {
      opacity: 0;
      transform: translate3d(var(--tooltip-slide-x), var(--tooltip-slide-y), 0) scale(0.98);
    }
    to {
      opacity: 1;
      transform: translate3d(0, 0, 0) scale(1);
    }
  }
  @keyframes tooltip-out {
    from {
      opacity: 1;
      transform: translate3d(0, 0, 0) scale(1);
    }
    to {
      opacity: 0;
      transform: translate3d(var(--tooltip-slide-x), var(--tooltip-slide-y), 0) scale(0.98);
    }
  }
</style>
<div data-slot="tooltip" class="inline-block">
  <button
    data-slot="tooltip-trigger"
    class="rounded-full border border-dashed border-gray-300 bg-white px-4 py-2
           text-gray-900 cursor-help underline decoration-dotted underline-offset-2"
  >
    Hover me
  </button>
  <div
    data-slot="tooltip-content"
    data-side="top"
    class="absolute
           inline-flex items-center gap-1.5 rounded-2xl bg-gray-900 px-3 py-1.5
           text-xs whitespace-nowrap text-white z-50
           opacity-0 pointer-events-none transition-opacity duration-150
           data-[open]:opacity-100 data-[open]:pointer-events-auto
           data-[instant]:transition-none data-[instant]:[animation:none]
           data-[side=top]:[--tooltip-slide-y:8px]
           data-[side=bottom]:[--tooltip-slide-y:-8px]
           data-[side=left]:[--tooltip-slide-x:8px]
           data-[side=right]:[--tooltip-slide-x:-8px]
           data-[side=inline-start]:[--tooltip-slide-x:8px]
           data-[side=inline-end]:[--tooltip-slide-x:-8px]"
  >
    Tooltip content
    <div
      data-slot="tooltip-arrow"
      class="absolute size-2.5 rounded-[2px] bg-gray-900 rotate-45
             data-[side=top]:-bottom-1
             data-[side=bottom]:-top-1
             data-[side=left]:-right-1 data-[side=left]:-translate-x-[1.5px]
             data-[side=right]:-left-1 data-[side=right]:translate-x-[1.5px]
             data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-x-[1.5px]
             data-[side=inline-end]:-left-1 data-[side=inline-end]:translate-x-[1.5px]"
    ></div>
  </div>
</div>

Popover

↓ interactive
View source
<div data-slot="popover" class="popover-root">
  <button data-slot="popover-trigger" class="popover-trigger-btn">Open Popover</button>
  <div data-slot="popover-content" data-side="bottom" data-align="center" class="popover-content" hidden>
    <div class="popover-title">Popover Panel</div>
    <p class="popover-text">
      Unlike tooltips, popovers stay open until dismissed.
      Click outside or press Escape to close.
    </p>
    <button data-slot="popover-close" class="popover-close-btn">Got it</button>
  </div>
</div>

<style>
  .popover-root { display: inline-block; }
  .popover-trigger-btn {
    padding: 0.5rem 1rem;
    background: #1a1a1a;
    color: #faf9f7;
    border: none;
    cursor: pointer;
  }
  .popover-content {
    position: fixed;
    background: #faf9f7;
    border: 1px solid #ccc;
    padding: 1rem;
    width: min(20rem, calc(100vw - 2rem));
    z-index: 50;
    transform-origin: var(--transform-origin, center);
    --popover-slide-x: 0px;
    --popover-slide-y: -4px;
  }
  .popover-content[data-side="top"] {
    --popover-slide-y: 4px;
  }
  .popover-content[data-side="bottom"] {
    --popover-slide-y: -4px;
  }
  .popover-content[data-side="left"] {
    --popover-slide-x: 4px;
    --popover-slide-y: 0px;
  }
  .popover-content[data-side="right"] {
    --popover-slide-x: -4px;
    --popover-slide-y: 0px;
  }
  .popover-content[data-open] {
    animation: popover-in 160ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  .popover-content[data-closed] {
    pointer-events: none;
    animation: popover-out 120ms ease-in forwards;
  }
  @keyframes popover-in {
    from {
      opacity: 0;
      scale: 0.96;
      translate: var(--popover-slide-x) var(--popover-slide-y);
    }
    to {
      opacity: 1;
      scale: 1;
      translate: 0 0;
    }
  }
  @keyframes popover-out {
    from {
      opacity: 1;
      scale: 1;
      translate: 0 0;
    }
    to {
      opacity: 0;
      scale: 0.96;
      translate: var(--popover-slide-x) var(--popover-slide-y);
    }
  }
  .popover-title { font-weight: 700; margin-bottom: 0.5rem; }
  .popover-text { color: #666; font-size: 0.9rem; margin-bottom: 0.75rem; }
  .popover-close-btn {
    padding: 0.3rem 0.6rem;
    background: none;
    border: 1px solid #ccc;
    cursor: pointer;
  }
</style>
<div data-slot="popover" class="relative inline-block">
  <button
    data-slot="popover-trigger"
    class="px-4 py-2 bg-gray-900 text-white border-none cursor-pointer
           hover:opacity-90 transition-opacity"
  >
    Open Popover
  </button>
  <div
    data-slot="popover-content"
    data-side="bottom"
    data-align="center"
    class="fixed bg-white border border-gray-300
           p-4 w-80 max-w-[calc(100vw-2rem)] z-50 rounded-lg shadow-lg"
    hidden
  >
    <div class="font-bold mb-2 mt-0">Popover Panel</div>
    <p class="text-gray-500 text-sm mb-3">
      Unlike tooltips, popovers stay open until dismissed.
      Click outside or press Escape to close.
    </p>
    <button
      data-slot="popover-close"
      class="px-2.5 py-1 bg-transparent border border-gray-300
             cursor-pointer hover:bg-code-bg transition-colors"
    >
      Got it
    </button>
  </div>
</div>

/* State-based animation (works with presence lifecycle) */
[data-slot="popover-content"] {
  transform-origin: var(--transform-origin, center);
  --popover-slide-x: 0px;
  --popover-slide-y: -4px;
}
[data-slot="popover-content"][data-side="top"] {
  --popover-slide-y: 4px;
}
[data-slot="popover-content"][data-side="bottom"] {
  --popover-slide-y: -4px;
}
[data-slot="popover-content"][data-side="left"] {
  --popover-slide-x: 4px;
  --popover-slide-y: 0px;
}
[data-slot="popover-content"][data-side="right"] {
  --popover-slide-x: -4px;
  --popover-slide-y: 0px;
}
[data-slot="popover-content"][data-open] {
  animation: popover-in 160ms cubic-bezier(0.16, 1, 0.3, 1);
}
[data-slot="popover-content"][data-closed] {
  pointer-events: none;
  animation: popover-out 120ms ease-in forwards;
}
@keyframes popover-in {
  from {
    opacity: 0;
    scale: 0.96;
    translate: var(--popover-slide-x) var(--popover-slide-y);
  }
  to {
    opacity: 1;
    scale: 1;
    translate: 0 0;
  }
}
@keyframes popover-out {
  from {
    opacity: 1;
    scale: 1;
    translate: 0 0;
  }
  to {
    opacity: 0;
    scale: 0.96;
    translate: var(--popover-slide-x) var(--popover-slide-y);
  }
}

Hover Card

↓ base-ui style delays: data-delay + data-close-delay
View source
<div data-slot="hover-card" data-delay="180" data-close-delay="120" class="hover-card-root">
  <button data-slot="hover-card-trigger" class="hover-card-trigger-btn">@data-slot</button>
  <div data-slot="hover-card-content" data-side="bottom" data-align="start" class="hover-card-content" hidden>
    <div class="hover-card-head">
      <div class="hover-card-avatar">DS</div>
      <div>
        <p class="hover-card-title">data-slot</p>
        <p class="hover-card-handle">@data-slot</p>
      </div>
    </div>
    <p class="hover-card-text">Headless UI components for vanilla JavaScript.</p>
    <p class="hover-card-meta">Hover/focus to preview, leave to close.</p>
  </div>
</div>

<style>
  .hover-card-root { display: inline-block; }
  .hover-card-trigger-btn {
    padding: 0.5rem 0.875rem;
    background: none;
    border: 1px dashed #ccc;
    cursor: pointer;
    font-weight: 600;
  }
  .hover-card-content {
    position: fixed;
    width: min(18rem, calc(100vw - 2rem));
    background: #faf9f7;
    border: 1px solid #ccc;
    border-radius: 0.65rem;
    padding: 0.75rem;
    z-index: 50;
    transform-origin: var(--transform-origin, center);
    --hover-card-slide-x: 0px;
    --hover-card-slide-y: -4px;
  }
  .hover-card-content[data-side="top"] { --hover-card-slide-y: 4px; }
  .hover-card-content[data-side="bottom"] { --hover-card-slide-y: -4px; }
  .hover-card-content[data-side="left"] {
    --hover-card-slide-x: 4px;
    --hover-card-slide-y: 0px;
  }
  .hover-card-content[data-side="right"] {
    --hover-card-slide-x: -4px;
    --hover-card-slide-y: 0px;
  }
  .hover-card-content[data-open] {
    animation: hover-card-in 160ms cubic-bezier(0.16, 1, 0.3, 1);
  }
  .hover-card-content[data-closed] {
    pointer-events: none;
    animation: hover-card-out 120ms ease-in forwards;
  }
  .hover-card-content[data-instant] {
    transition: none;
    animation: none;
  }
  @keyframes hover-card-in {
    from {
      opacity: 0;
      scale: 0.96;
      translate: var(--hover-card-slide-x) var(--hover-card-slide-y);
    }
    to {
      opacity: 1;
      scale: 1;
      translate: 0 0;
    }
  }
  @keyframes hover-card-out {
    from {
      opacity: 1;
      scale: 1;
      translate: 0 0;
    }
    to {
      opacity: 0;
      scale: 0.96;
      translate: var(--hover-card-slide-x) var(--hover-card-slide-y);
    }
  }
  .hover-card-head {
    display: flex;
    align-items: center;
    gap: 0.625rem;
  }
  .hover-card-avatar {
    width: 2rem;
    height: 2rem;
    border-radius: 9999px;
    display: grid;
    place-items: center;
    background: #1a1a1a;
    color: #faf9f7;
    font-size: 0.75rem;
    font-weight: 700;
  }
  .hover-card-title {
    font-weight: 700;
    margin: 0;
    line-height: 1.15;
  }
  .hover-card-handle {
    margin: 0;
    color: #666;
    font-size: 0.82rem;
  }
  .hover-card-text {
    margin: 0.7rem 0 0;
    font-size: 0.9rem;
  }
  .hover-card-meta {
    margin: 0.35rem 0 0;
    font-size: 0.78rem;
    color: #666;
  }
</style>
<div data-slot="hover-card" data-delay="180" data-close-delay="120" class="inline-block">
  <button
    data-slot="hover-card-trigger"
    class="px-3.5 py-2 bg-transparent border border-dashed border-gray-300
           cursor-pointer font-semibold hover:bg-code-bg transition-colors"
  >
    @data-slot
  </button>

  <div
    data-slot="hover-card-content"
    data-side="bottom"
    data-align="start"
    class="fixed w-72 max-w-[calc(100vw-2rem)] bg-white border border-gray-300
           rounded-xl p-3 z-50 shadow-lg"
    hidden
  >
    <div class="flex items-center gap-2.5">
      <div class="w-8 h-8 rounded-full grid place-items-center bg-gray-900 text-white text-xs font-bold">DS</div>
      <div>
        <p class="font-bold leading-tight">data-slot</p>
        <p class="text-gray-500 text-xs">@data-slot</p>
      </div>
    </div>
    <p class="mt-3 text-sm">Headless UI components for vanilla JavaScript.</p>
    <p class="mt-1 text-xs text-gray-500">Hover/focus to preview, leave to close.</p>
  </div>
</div>

/* Animation hooks */
[data-slot="hover-card-content"] {
  transform-origin: var(--transform-origin, center);
  --hover-card-slide-x: 0px;
  --hover-card-slide-y: -4px;
}
[data-slot="hover-card-content"][data-side="top"] {
  --hover-card-slide-y: 4px;
}
[data-slot="hover-card-content"][data-side="bottom"] {
  --hover-card-slide-y: -4px;
}
[data-slot="hover-card-content"][data-side="left"] {
  --hover-card-slide-x: 4px;
  --hover-card-slide-y: 0px;
}
[data-slot="hover-card-content"][data-side="right"] {
  --hover-card-slide-x: -4px;
  --hover-card-slide-y: 0px;
}
[data-slot="hover-card-content"][data-open] {
  animation: hover-card-in 160ms cubic-bezier(0.16, 1, 0.3, 1);
}
[data-slot="hover-card-content"][data-closed] {
  pointer-events: none;
  animation: hover-card-out 120ms ease-in forwards;
}
[data-slot="hover-card-content"][data-instant] {
  transition: none;
  animation: none;
}

Navigation Menu

↓ mega menu with directional animations

Hover between items. The popup shell animates on full open and close, and panel switches resize the shell with CSS while the content slides.

CSS variables: --popup-width, --popup-height, --positioner-width, --positioner-height, --available-width, --available-height, --transform-origin.

Legacy --viewport-width and --viewport-height are still emitted, but the demo uses the popup shell as the canonical sizing surface.

Use data-align="start|center|end" on items to control alignment.

For sticky headers, opt into viewport anchoring with data-position-method="fixed" or createNavigationMenu(root, { positionMethod: "fixed" }).

View source
<!-- Add data-position-method="fixed" for sticky headers -->
<nav data-slot="navigation-menu" class="nav-menu" data-side-offset="8">
  <ul data-slot="navigation-menu-list" class="nav-menu-list">
    <li data-slot="navigation-menu-item" data-value="products" class="nav-menu-item">
      <button data-slot="navigation-menu-trigger" class="nav-menu-trigger">Products</button>
      <div data-slot="navigation-menu-content" class="nav-menu-content" hidden>
        <div class="nav-menu-grid">
          <a href="#" class="nav-menu-link">
            <div class="nav-menu-link-title">Analytics</div>
            <div class="nav-menu-link-desc">Real-time metrics</div>
          </a>
          <!-- More links... -->
        </div>
      </div>
    </li>
    <li data-slot="navigation-menu-item" class="nav-menu-item">
      <a href="#" class="nav-menu-trigger nav-menu-trigger-plain">Docs</a>
    </li>
    <!-- More submenu items... -->
    <div data-slot="navigation-menu-indicator" class="nav-menu-indicator"></div>
  </ul>
  <div data-slot="navigation-menu-portal">
    <div data-slot="navigation-menu-positioner" class="nav-menu-positioner">
      <div data-slot="navigation-menu-popup" class="nav-menu-popup" hidden>
        <div data-slot="navigation-menu-viewport" class="nav-menu-viewport" hidden></div>
      </div>
    </div>
  </div>
</nav>

<style>
  .nav-menu { position: relative; }
  .nav-menu-list { display: flex; list-style: none; position: relative; }
  .nav-menu-trigger {
    padding: 0.6rem 1rem;
    background: none;
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.35rem;
    text-decoration: none;
    color: inherit;
  }
  .nav-menu-trigger::after { content: ""; font-size: 0.6rem; }
  .nav-menu-trigger[data-state="open"]::after { transform: rotate(180deg); }
  .nav-menu-trigger-plain::after { content: none; }

  .nav-menu-positioner {
    box-sizing: border-box;
    width: var(--positioner-width);
    height: var(--positioner-height);
    max-width: var(--available-width);
    top: 0;
    left: 0;
    transition: top 0.22s cubic-bezier(0.32, 0.72, 0, 1),
                left 0.22s cubic-bezier(0.32, 0.72, 0, 1);
  }
  .nav-menu-positioner[data-instant] { transition: none; }

  .nav-menu-popup {
    position: relative;
    box-sizing: border-box;
    width: var(--popup-width);
    height: var(--popup-height);
    transform-origin: var(--transform-origin);
    background: #faf9f7;
    border-radius: 8px;
    box-shadow: 0 4px 24px rgba(0,0,0,0.12);
    overflow: hidden;
    transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1),
                width 0.16s ease,
                height 0.16s ease,
                opacity 0.16s ease;
  }
  .nav-menu-popup[data-instant] {
    transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1),
                opacity 0.16s ease;
  }
  .nav-menu-popup[data-starting-style],
  .nav-menu-popup[data-ending-style] {
    opacity: 0;
    transform: scale(0.96);
  }
  .nav-menu-popup[data-closed]:not([data-ending-style]) {
    pointer-events: none;
  }

  .nav-menu-viewport { position: relative; overflow: hidden; width: 100%; height: 100%; }
  .nav-menu-content {
    position: relative;
    transform: translateX(0);
    transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1),
                opacity 0.16s ease;
    will-change: transform, opacity;
  }
  .nav-menu-content[data-closed]:not([data-ending-style]) {
    opacity: 0;
    pointer-events: none;
  }
  .nav-menu-content[data-starting-style],
  .nav-menu-content[data-ending-style] { opacity: 0; }
  .nav-menu-content[data-starting-style][data-activation-direction="right"] {
    transform: translateX(24px);
  }
  .nav-menu-content[data-starting-style][data-activation-direction="left"] {
    transform: translateX(-24px);
  }
  .nav-menu-content[data-ending-style][data-activation-direction="right"] {
    transform: translateX(-24px);
  }
  .nav-menu-content[data-ending-style][data-activation-direction="left"] {
    transform: translateX(24px);
  }
  .nav-menu-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem; width: 380px; padding: 0.75rem; }
</style>
<!-- Add data-position-method="fixed" for sticky headers -->
<nav data-slot="navigation-menu" class="relative" data-side-offset="8">
  <ul data-slot="navigation-menu-list" class="flex relative list-none">
    <li data-slot="navigation-menu-item" data-value="products" class="static">
      <button
        data-slot="navigation-menu-trigger"
        class="px-4 py-2.5 bg-transparent border-none cursor-pointer
               flex items-center gap-1.5 transition-colors relative z-1
               hover:text-accent data-[state=open]:text-accent
               after:content-['▼'] after:text-[0.6rem]
               after:transition-transform after:duration-200
               data-[state=open]:after:rotate-180"
      >
        Products
      </button>
      <div
        data-slot="navigation-menu-content"
        class="nav-menu-tw-content z-10"
        hidden
      >
        <div class="grid grid-cols-2 gap-3 w-[380px] p-2">
          <a href="#" class="block p-3 rounded transition-colors hover:bg-code-bg text-inherit">
            <div class="mb-1 font-medium">Analytics</div>
            <div class="text-sm text-muted">Real-time metrics</div>
          </a>
          <!-- More links... -->
        </div>
      </div>
    </li>
    <li data-slot="navigation-menu-item" class="static">
      <a
        href="#"
        class="px-4 py-2.5 bg-transparent border-none cursor-pointer
               flex items-center gap-1.5 transition-colors relative z-1
               hover:text-accent text-inherit no-underline"
      >
        Docs
      </a>
    </li>
    <!-- More submenu items... -->
    <div
      data-slot="navigation-menu-indicator"
      class="absolute bg-code-bg z-0 rounded-md transition-all duration-150
             pointer-events-none opacity-0 data-[state=visible]:opacity-100"
      style="left: var(--indicator-left, 0); top: var(--indicator-top, 0);
             width: var(--indicator-width, 0); height: var(--indicator-height, 0);"
    ></div>
  </ul>
  <div data-slot="navigation-menu-portal">
    <div data-slot="navigation-menu-positioner" class="nav-menu-tw-positioner z-50">
      <div
        data-slot="navigation-menu-popup"
        class="nav-menu-tw-popup relative overflow-hidden rounded-lg border border-border bg-bg shadow-[0_4px_24px_rgba(0,0,0,0.12)]"
        hidden
        style="width: var(--popup-width); height: var(--popup-height); transform-origin: var(--transform-origin);"
      >
        <div data-slot="navigation-menu-viewport" class="relative h-full w-full overflow-hidden" hidden></div>
      </div>
    </div>
  </div>
</nav>

/* Because the popup stack is portaled, target the moving parts directly. */
.nav-menu-tw-positioner {
  box-sizing: border-box;
  width: var(--positioner-width);
  height: var(--positioner-height);
  max-width: var(--available-width);
  transition: top 0.22s cubic-bezier(0.32, 0.72, 0, 1),
              left 0.22s cubic-bezier(0.32, 0.72, 0, 1);
}
.nav-menu-tw-positioner[data-instant] {
  transition: none;
}
.nav-menu-tw-popup {
  position: relative;
  box-sizing: border-box;
  transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1),
              width 0.16s ease,
              height 0.16s ease,
              opacity 0.16s ease;
  will-change: transform, width, height;
}
.nav-menu-tw-popup[data-instant] {
  transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1),
              opacity 0.16s ease;
}
.nav-menu-tw-popup[data-starting-style],
.nav-menu-tw-popup[data-ending-style] {
  opacity: 0;
  transform: scale(0.96);
}
.nav-menu-tw-popup[data-closed]:not([data-ending-style]) {
  pointer-events: none;
}
.nav-menu-tw-content[data-closed]:not([data-ending-style]) {
  opacity: 0;
  pointer-events: none;
}
.nav-menu-tw-content {
  position: relative;
  transform: translateX(0);
  transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1),
              opacity 0.16s ease;
  will-change: transform, opacity;
}
.nav-menu-tw-content[data-starting-style],
.nav-menu-tw-content[data-ending-style] {
  opacity: 0;
}
.nav-menu-tw-content[data-starting-style][data-activation-direction="right"] {
  transform: translateX(24px);
}
.nav-menu-tw-content[data-starting-style][data-activation-direction="left"] {
  transform: translateX(-24px);
}
.nav-menu-tw-content[data-ending-style][data-activation-direction="right"] {
  transform: translateX(-24px);
}
.nav-menu-tw-content[data-ending-style][data-activation-direction="left"] {
  transform: translateX(24px);
}

Dropdown Menu

↓ interactive
View source
<div data-slot="dropdown-menu" data-align="center" class="dropdown-root">
  <button data-slot="dropdown-menu-trigger" class="dropdown-trigger">
    Actions ▼
  </button>
  <div data-slot="dropdown-menu-content" class="dropdown-content" hidden>
    <div data-slot="dropdown-menu-group">
      <div data-slot="dropdown-menu-label" class="dropdown-label">Edit</div>
      <button data-slot="dropdown-menu-item" data-value="cut" class="dropdown-item">
        Cut
        <span data-slot="dropdown-menu-shortcut" class="dropdown-shortcut">⌘X</span>
      </button>
      <button data-slot="dropdown-menu-item" data-value="copy" class="dropdown-item">
        Copy
        <span data-slot="dropdown-menu-shortcut" class="dropdown-shortcut">⌘C</span>
      </button>
      <button data-slot="dropdown-menu-item" data-value="paste" class="dropdown-item">
        Paste
        <span data-slot="dropdown-menu-shortcut" class="dropdown-shortcut">⌘V</span>
      </button>
    </div>
    <div data-slot="dropdown-menu-separator" class="dropdown-separator"></div>
    <button data-slot="dropdown-menu-item" data-value="delete" data-variant="destructive" class="dropdown-item destructive">
      Delete
    </button>
    <button data-slot="dropdown-menu-item" data-disabled class="dropdown-item disabled">
      Archive (coming soon)
    </button>
  </div>
</div>

<style>
  .dropdown-root { position: relative; display: inline-block; }
  .dropdown-trigger {
    padding: 0.5rem 1rem;
    background: #1a1a1a;
    color: #faf9f7;
    border: none;
    cursor: pointer;
  }
  .dropdown-content {
    position: absolute;
    top: 100%;
    left: 0;
    margin-top: 0.25rem;
    background: #faf9f7;
    border: 1px solid #ccc;
    min-width: 180px;
    padding: 0.25rem;
    z-index: 50;
  }
  .dropdown-label {
    padding: 0.375rem 0.5rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: #888;
  }
  .dropdown-item {
    display: flex;
    align-items: center;
    width: 100%;
    padding: 0.375rem 0.5rem;
    border: none;
    background: none;
    cursor: pointer;
    font-size: 0.875rem;
    text-align: left;
  }
  .dropdown-item[data-highlighted] {
    background: #e5e5e5;
  }
  .dropdown-item.destructive { color: #dc2626; }
  .dropdown-item.destructive[data-highlighted] {
    background: #fef2f2;
  }
  .dropdown-item.disabled {
    color: #aaa;
    cursor: not-allowed;
  }
  .dropdown-shortcut {
    margin-left: auto;
    font-size: 0.75rem;
    color: #888;
  }
  .dropdown-separator {
    height: 1px;
    background: #ddd;
    margin: 0.25rem 0;
  }
</style>
<div data-slot="dropdown-menu" class="relative inline-block">
  <button
    data-slot="dropdown-menu-trigger"
    class="px-4 py-2 bg-gray-900 text-white border-none cursor-pointer
           hover:opacity-90 transition-opacity"
  >
    Actions ▼
  </button>
  <div
    data-slot="dropdown-menu-content"
    class="absolute top-full left-0 mt-1 bg-white border border-gray-300
           min-w-[180px] p-1 z-50 rounded-lg shadow-lg"
    hidden
  >
    <div data-slot="dropdown-menu-group">
      <div
        data-slot="dropdown-menu-label"
        class="px-2 py-1.5 text-xs font-semibold text-gray-400"
      >
        Edit
      </div>
      <button
        data-slot="dropdown-menu-item"
        data-value="cut"
        class="flex items-center w-full px-2 py-1.5 text-sm text-left rounded
               data-[highlighted]:bg-gray-100"
      >
        Cut
        <span data-slot="dropdown-menu-shortcut" class="ml-auto text-xs text-gray-400">⌘X</span>
      </button>
      <button
        data-slot="dropdown-menu-item"
        data-value="copy"
        class="flex items-center w-full px-2 py-1.5 text-sm text-left rounded
               data-[highlighted]:bg-gray-100"
      >
        Copy
        <span data-slot="dropdown-menu-shortcut" class="ml-auto text-xs text-gray-400">⌘C</span>
      </button>
      <button
        data-slot="dropdown-menu-item"
        data-value="paste"
        class="flex items-center w-full px-2 py-1.5 text-sm text-left rounded
               data-[highlighted]:bg-gray-100"
      >
        Paste
        <span data-slot="dropdown-menu-shortcut" class="ml-auto text-xs text-gray-400">⌘V</span>
      </button>
    </div>
    <div data-slot="dropdown-menu-separator" class="h-px bg-gray-200 my-1"></div>
    <button
      data-slot="dropdown-menu-item"
      data-value="delete"
      data-variant="destructive"
      class="flex items-center w-full px-2 py-1.5 text-sm text-left rounded
             text-red-600 data-[highlighted]:bg-red-50"
    >
      Delete
    </button>
    <button
      data-slot="dropdown-menu-item"
      data-disabled
      class="flex items-center w-full px-2 py-1.5 text-sm text-left rounded
             text-gray-300 cursor-not-allowed"
    >
      Archive (coming soon)
    </button>
  </div>
</div>

Select

↓ interactive
View source
<label for="fruit-select" class="select-field-label">Fruit</label>
<div data-slot="select" data-placeholder="Select a fruit..." class="select-root">
  <button data-slot="select-trigger" id="fruit-select" class="select-trigger">
    <span data-slot="select-value"></span>
    <span class="select-icon"></span>
  </button>
  <div data-slot="select-content" class="select-content" hidden>
    <div data-slot="select-group">
      <div data-slot="select-label" class="select-label">Fruits</div>
      <div data-slot="select-item" data-value="apple" data-label="Apple" class="select-item">
        <span data-slot="select-item-text">Apple</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="banana" data-label="Banana" class="select-item">
        <span data-slot="select-item-text">Banana</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="orange" data-label="Orange" class="select-item">
        <span data-slot="select-item-text">Orange</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="mango" data-label="Mango" class="select-item">
        <span data-slot="select-item-text">Mango</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="grape" data-label="Grape" class="select-item">
        <span data-slot="select-item-text">Grape</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="pear" data-label="Pear" class="select-item">
        <span data-slot="select-item-text">Pear</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="peach" data-label="Peach" class="select-item">
        <span data-slot="select-item-text">Peach</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="pineapple" data-label="Pineapple" class="select-item">
        <span data-slot="select-item-text">Pineapple</span><span class="select-check"></span>
      </div>
    </div>
    <div data-slot="select-separator" class="select-separator"></div>
    <div data-slot="select-group">
      <div data-slot="select-label" class="select-label">Vegetables</div>
      <div data-slot="select-item" data-value="carrot" data-label="Carrot" class="select-item">
        <span data-slot="select-item-text">Carrot</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="broccoli" data-label="Broccoli" class="select-item">
        <span data-slot="select-item-text">Broccoli</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="cucumber" data-label="Cucumber" class="select-item">
        <span data-slot="select-item-text">Cucumber</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="tomato" data-label="Tomato" class="select-item">
        <span data-slot="select-item-text">Tomato</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="pepper" data-label="Bell Pepper" class="select-item">
        <span data-slot="select-item-text">Bell Pepper</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="zucchini" data-label="Zucchini" class="select-item">
        <span data-slot="select-item-text">Zucchini</span><span class="select-check"></span>
      </div>
      <div data-slot="select-item" data-value="spinach" data-label="Spinach" data-disabled class="select-item disabled">
        <span data-slot="select-item-text">Spinach (out of stock)</span><span class="select-check"></span>
      </div>
    </div>
  </div>
</div>

<style>
  .select-root { position: relative; display: inline-block; }
  .select-field-label {
    display: block;
    font-size: 0.875rem;
    font-weight: 500;
    color: #333;
    margin-bottom: 0.25rem;
  }
  .select-trigger {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    min-width: 180px;
    background: #1a1a1a;
    color: #faf9f7;
    border: none;
    cursor: pointer;
  }
  .select-trigger[data-placeholder] [data-slot="select-value"] {
    color: #888;
  }
  .select-icon {
    margin-left: auto;
    font-size: 0.75rem;
  }
  .select-content {
    background: #faf9f7;
    border: 1px solid #ccc;
    min-width: 180px;
    max-height: 220px;
    overflow-y: auto;
    padding: 0.25rem;
    z-index: 50;
  }
  .select-label {
    padding: 0.375rem 0.5rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: #888;
  }
  .select-item {
    display: flex;
    align-items: center;
    width: 100%;
    padding: 0.375rem 0.5rem;
    cursor: pointer;
    font-size: 0.875rem;
  }
  .select-check {
    margin-left: auto;
    visibility: hidden;
  }
  .select-item[data-selected] .select-check {
    visibility: visible;
  }
  .select-item[data-highlighted] {
    background: #e5e5e5;
  }
  .select-item.disabled {
    color: #aaa;
    cursor: not-allowed;
  }
  .select-separator {
    height: 1px;
    background: #ddd;
    margin: 0.25rem 0;
  }
</style>
<label for="fruit-select" class="block mb-1 text-sm font-medium text-gray-700">Fruit</label>
<div data-slot="select" data-placeholder="Select a fruit..." class="inline-block relative">
  <button
    data-slot="select-trigger"
    id="fruit-select"
    class="flex items-center gap-2 px-4 py-2 min-w-[180px] bg-gray-900 text-white
           border-none cursor-pointer hover:opacity-90 transition-opacity"
  >
    <span data-slot="select-value" class="data-[placeholder]:text-gray-400"></span>
    <span class="ml-auto text-xs"></span>
  </button>
  <div
    data-slot="select-content"
    class="absolute top-full left-0 mt-1 bg-white border border-gray-300
           min-w-[180px] max-h-[220px] overflow-y-auto p-1 z-50 rounded-lg shadow-lg"
    hidden
  >
    <div data-slot="select-group">
      <div data-slot="select-label" class="px-2 py-1.5 text-xs font-semibold text-gray-400">
        Fruits
      </div>
      <div
        data-slot="select-item"
        data-value="apple"
        data-label="Apple"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Apple</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="banana"
        data-label="Banana"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Banana</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="orange"
        data-label="Orange"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Orange</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="mango"
        data-label="Mango"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Mango</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="grape"
        data-label="Grape"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Grape</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="pear"
        data-label="Pear"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Pear</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="peach"
        data-label="Peach"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Peach</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="pineapple"
        data-label="Pineapple"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Pineapple</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
    </div>
    <div data-slot="select-separator" class="my-1 h-px bg-gray-200"></div>
    <div data-slot="select-group">
      <div data-slot="select-label" class="px-2 py-1.5 text-xs font-semibold text-gray-400">
        Vegetables
      </div>
      <div
        data-slot="select-item"
        data-value="carrot"
        data-label="Carrot"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Carrot</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="broccoli"
        data-label="Broccoli"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Broccoli</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="cucumber"
        data-label="Cucumber"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Cucumber</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="tomato"
        data-label="Tomato"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Tomato</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="pepper"
        data-label="Bell Pepper"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Bell Pepper</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="zucchini"
        data-label="Zucchini"
        class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
               data-[highlighted]:bg-gray-100"
      >
        <span data-slot="select-item-text">Zucchini</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
      <div
        data-slot="select-item"
        data-value="spinach"
        data-label="Spinach"
        data-disabled
        class="flex items-center px-2 py-1.5 w-full text-sm text-gray-300 rounded cursor-not-allowed group"
      >
        <span data-slot="select-item-text">Spinach (out of stock)</span><span class="ml-auto invisible group-data-[selected]:visible"></span>
      </div>
    </div>
  </div>
</div>

Combobox

↓ interactive
View source
<label for="fruit-combo" class="combobox-field-label">Fruit</label>
<div data-slot="combobox" data-placeholder="Search fruits..." class="combobox-root">
  <div class="combobox-input-wrapper">
    <input data-slot="combobox-input" id="fruit-combo" class="combobox-input" />
    <button data-slot="combobox-trigger" class="combobox-trigger-btn"></button>
  </div>
  <div data-slot="combobox-content" class="combobox-content" hidden>
    <div data-slot="combobox-list">
      <div data-slot="combobox-empty" class="combobox-empty" hidden>No results found</div>
      <div data-slot="combobox-group">
        <div data-slot="combobox-label" class="combobox-label">Fruits</div>
        <div data-slot="combobox-item" data-value="apple" data-label="Apple" class="combobox-item">
          Apple<span class="combobox-check"></span>
        </div>
        <div data-slot="combobox-item" data-value="banana" data-label="Banana" class="combobox-item">
          Banana<span class="combobox-check"></span>
        </div>
        <div data-slot="combobox-item" data-value="orange" data-label="Orange" class="combobox-item">
          Orange<span class="combobox-check"></span>
        </div>
      </div>
      <div data-slot="combobox-separator" class="combobox-separator"></div>
      <div data-slot="combobox-group">
        <div data-slot="combobox-label" class="combobox-label">Vegetables</div>
        <div data-slot="combobox-item" data-value="carrot" data-label="Carrot" class="combobox-item">
          Carrot<span class="combobox-check"></span>
        </div>
        <div data-slot="combobox-item" data-value="broccoli" data-label="Broccoli" class="combobox-item">
          Broccoli<span class="combobox-check"></span>
        </div>
        <div data-slot="combobox-item" data-value="spinach" data-label="Spinach (out of stock)" data-disabled class="combobox-item disabled">
          Spinach (out of stock)<span class="combobox-check"></span>
        </div>
      </div>
    </div>
  </div>
</div>

<style>
  .combobox-root { display: inline-block; }
  .combobox-field-label {
    display: block;
    font-size: 0.875rem;
    font-weight: 500;
    color: #333;
    margin-bottom: 0.25rem;
  }
  .combobox-input-wrapper {
    display: flex;
    align-items: center;
    background: #1a1a1a;
    min-width: 220px;
  }
  .combobox-input {
    flex: 1;
    padding: 0.5rem 0.75rem;
    background: transparent;
    color: #faf9f7;
    border: none;
    outline: none;
    font-size: 0.875rem;
  }
  .combobox-input::placeholder { color: #888; }
  .combobox-trigger-btn {
    padding: 0.5rem;
    background: transparent;
    color: #888;
    border: none;
    cursor: pointer;
    font-size: 0.75rem;
  }
  .combobox-content {
    background: #faf9f7;
    border: 1px solid #ccc;
    min-width: 220px;
    max-height: 200px;
    overflow-y: auto;
    padding: 0.25rem;
    z-index: 50;
  }
  .combobox-label {
    padding: 0.375rem 0.5rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: #888;
  }
  .combobox-item {
    display: flex;
    align-items: center;
    width: 100%;
    padding: 0.375rem 0.5rem;
    cursor: pointer;
    font-size: 0.875rem;
  }
  .combobox-check {
    margin-left: auto;
    visibility: hidden;
  }
  .combobox-item[data-selected] .combobox-check {
    visibility: visible;
  }
  .combobox-item[data-highlighted] {
    background: #e5e5e5;
  }
  .combobox-item.disabled {
    color: #aaa;
    cursor: not-allowed;
  }
  .combobox-empty {
    padding: 0.5rem;
    text-align: center;
    font-size: 0.875rem;
    color: #888;
  }
  .combobox-separator {
    height: 1px;
    background: #ddd;
    margin: 0.25rem 0;
  }
</style>
<label for="fruit-combo" class="block text-sm font-medium text-gray-700 mb-1">Fruit</label>
<div data-slot="combobox" data-placeholder="Search fruits..." class="relative inline-block">
  <div class="flex items-center bg-gray-900 min-w-[220px]">
    <input
      data-slot="combobox-input"
      id="fruit-combo"
      class="flex-1 px-3 py-2 bg-transparent text-white border-none outline-none text-sm
             placeholder:text-gray-400"
    />
    <button
      data-slot="combobox-trigger"
      class="px-2 py-2 bg-transparent text-gray-400 border-none cursor-pointer text-xs"
    ></button>
  </div>
  <div
    data-slot="combobox-content"
    class="bg-white border border-gray-300 min-w-[220px] max-h-[200px] overflow-y-auto
           p-1 z-50 rounded-lg shadow-lg"
    hidden
  >
    <div data-slot="combobox-list">
      <div data-slot="combobox-empty" class="py-2 text-center text-sm text-gray-400" hidden>
        No results found
      </div>
      <div data-slot="combobox-group">
        <div data-slot="combobox-label" class="px-2 py-1.5 text-xs font-semibold text-gray-400">
          Fruits
        </div>
        <div
          data-slot="combobox-item"
          data-value="apple"
          data-label="Apple"
          class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
                 data-[highlighted]:bg-gray-100"
        >
          Apple<span class="ml-auto invisible group-data-[selected]:visible"></span>
        </div>
        <div
          data-slot="combobox-item"
          data-value="banana"
          data-label="Banana"
          class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
                 data-[highlighted]:bg-gray-100"
        >
          Banana<span class="ml-auto invisible group-data-[selected]:visible"></span>
        </div>
        <div
          data-slot="combobox-item"
          data-value="orange"
          data-label="Orange"
          class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
                 data-[highlighted]:bg-gray-100"
        >
          Orange<span class="ml-auto invisible group-data-[selected]:visible"></span>
        </div>
      </div>
      <div data-slot="combobox-separator" class="h-px bg-gray-200 my-1"></div>
      <div data-slot="combobox-group">
        <div data-slot="combobox-label" class="px-2 py-1.5 text-xs font-semibold text-gray-400">
          Vegetables
        </div>
        <div
          data-slot="combobox-item"
          data-value="carrot"
          data-label="Carrot"
          class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
                 data-[highlighted]:bg-gray-100"
        >
          Carrot<span class="ml-auto invisible group-data-[selected]:visible"></span>
        </div>
        <div
          data-slot="combobox-item"
          data-value="broccoli"
          data-label="Broccoli"
          class="group flex items-center w-full px-2 py-1.5 text-sm rounded cursor-pointer
                 data-[highlighted]:bg-gray-100"
        >
          Broccoli<span class="ml-auto invisible group-data-[selected]:visible"></span>
        </div>
        <div
          data-slot="combobox-item"
          data-value="spinach"
          data-label="Spinach (out of stock)"
          data-disabled
          class="group flex items-center w-full px-2 py-1.5 text-sm rounded
                 text-gray-300 cursor-not-allowed"
        >
          Spinach (out of stock)<span class="ml-auto invisible group-data-[selected]:visible"></span>
        </div>
      </div>
    </div>
  </div>
</div>

Command

↓ interactive
Project
Open Dashboard
Invite Teammate ⌘I
Preferences
Open Settings ⌘,
Billing Portal Soon
Project
Open Dashboard
Invite Teammate ⌘I
Preferences
Open Settings ⌘,
Billing Portal Soon

Inline palettes work as always-open searchable lists. Active state is exposed through data-selected and aria-selected. Keyboard navigation is handled at the palette root, without forcing focus into the input on generic box clicks.

View source
<label for="command-inline" class="command-field-label">Workspace</label>
<div data-slot="command" data-label="Workspace Command Menu" class="command-root">
  <div data-slot="command-input-wrapper" class="command-input-shell">
    <span class="command-search-icon"></span>
    <input
      data-slot="command-input"
      id="command-inline"
      class="command-input"
      placeholder="Search commands..."
    />
  </div>
  <div data-slot="command-list" class="command-list">
    <div data-slot="command-empty" class="command-empty" hidden>No results.</div>

    <div data-slot="command-group" class="command-group">
      <div data-slot="command-group-heading" class="command-heading">Project</div>
      <div data-slot="command-item" class="command-item">
        Open Dashboard
        <span data-slot="command-shortcut" class="command-shortcut"></span>
      </div>
      <div data-slot="command-item" data-keywords="invite,team" class="command-item">
        Invite Teammate
        <span data-slot="command-shortcut" class="command-shortcut">⌘I</span>
      </div>
    </div>

    <div data-slot="command-separator" class="command-separator"></div>

    <div data-slot="command-group" class="command-group">
      <div data-slot="command-group-heading" class="command-heading">Preferences</div>
      <div data-slot="command-item" data-value="settings" class="command-item">
        Open Settings
        <span data-slot="command-shortcut" class="command-shortcut">⌘,</span>
      </div>
      <div data-slot="command-item" data-disabled class="command-item command-item-disabled">
        Billing Portal
        <span data-slot="command-shortcut" class="command-shortcut">Soon</span>
      </div>
    </div>
  </div>
</div>
<label for="command-inline" class="mb-2 block text-[0.78rem] font-semibold uppercase tracking-[0.08em] text-stone-600">
  Workspace
</label>
<div
  data-slot="command"
  data-label="Workspace Command Menu"
  class="w-full max-w-[26rem] border border-stone-300 bg-[linear-gradient(180deg,#faf9f7_0%,#f4f0ea_100%)] shadow-[0_18px_40px_rgba(34,27,17,0.08)]"
>
  <div
    data-slot="command-input-wrapper"
    class="flex items-center gap-2 border-b border-stone-300/60 px-4 py-3"
  >
    <span class="text-sm text-stone-500"></span>
    <input
      data-slot="command-input"
      id="command-inline"
      class="w-full border-none bg-transparent text-[0.95rem] text-stone-950 outline-none placeholder:text-stone-400"
      placeholder="Search commands..."
    />
  </div>
  <div data-slot="command-list" class="max-h-72 overflow-auto p-1.5">
    <div data-slot="command-empty" class="px-3 py-4 text-center text-sm text-stone-500" hidden>No results.</div>
    <div data-slot="command-group">
      <div data-slot="command-group-heading" class="px-3 py-2 text-[0.72rem] font-bold uppercase tracking-[0.08em] text-stone-500">
        Project
      </div>
      <div
        data-slot="command-item"
        class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-stone-950 data-[selected]:bg-stone-200"
      >
        Open Dashboard
        <span data-slot="command-shortcut" class="ml-auto text-[0.72rem] uppercase tracking-[0.08em] text-stone-500"></span>
      </div>
      <div
        data-slot="command-item"
        data-keywords="invite,team"
        class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-stone-950 data-[selected]:bg-stone-200"
      >
        Invite Teammate
        <span data-slot="command-shortcut" class="ml-auto text-[0.72rem] uppercase tracking-[0.08em] text-stone-500">⌘I</span>
      </div>
    </div>
    <div data-slot="command-separator" class="my-1 h-px bg-stone-300/70"></div>
    <div data-slot="command-group">
      <div data-slot="command-group-heading" class="px-3 py-2 text-[0.72rem] font-bold uppercase tracking-[0.08em] text-stone-500">
        Preferences
      </div>
      <div
        data-slot="command-item"
        data-value="settings"
        class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-stone-950 data-[selected]:bg-stone-200"
      >
        Open Settings
        <span data-slot="command-shortcut" class="ml-auto text-[0.72rem] uppercase tracking-[0.08em] text-stone-500">⌘,</span>
      </div>
      <div
        data-slot="command-item"
        data-disabled
        class="flex cursor-default items-center gap-3 px-3 py-2.5 text-sm text-stone-400"
      >
        Billing Portal
        <span data-slot="command-shortcut" class="ml-auto text-[0.72rem] uppercase tracking-[0.08em] text-stone-400">Soon</span>
      </div>
    </div>
  </div>
</div>
↓ interactive

Compose @data-slot/command inside dialog-content to build modal palettes with the same strict cmdk-style focus behavior.

View source
<div data-slot="dialog" class="command-dialog">
  <button data-slot="dialog-trigger" class="command-dialog-trigger">Open Command Palette</button>
  <div data-slot="dialog-overlay" class="command-dialog-overlay" hidden></div>
  <div data-slot="dialog-content" class="command-dialog-panel" hidden>
    <h2 data-slot="dialog-title" class="command-dialog-title">Command Palette</h2>
    <p data-slot="dialog-description" class="command-dialog-description">
      Search for a command to run across your workspace.
    </p>

    <div data-slot="command" data-label="Global Command Palette" class="command-root command-root-dialog">
      <div data-slot="command-input-wrapper" class="command-input-shell">
        <span class="command-search-icon"></span>
        <input data-slot="command-input" class="command-input" placeholder="Search commands..." />
      </div>
      <div data-slot="command-list" class="command-list">
        <div data-slot="command-empty" class="command-empty" hidden>No results.</div>
        <div data-slot="command-item" class="command-item">
          New Issue
          <span data-slot="command-shortcut" class="command-shortcut">⌘N</span>
        </div>
        <div data-slot="command-item" class="command-item">
          Jump to Inbox
          <span data-slot="command-shortcut" class="command-shortcut">G I</span>
        </div>
        <div data-slot="command-item" class="command-item">
          Open Settings
          <span data-slot="command-shortcut" class="command-shortcut">⌘,</span>
        </div>
      </div>
    </div>
  </div>
</div>
<div data-slot="dialog">
  <button
    data-slot="dialog-trigger"
    class="border border-stone-950 bg-stone-950 px-4 py-2 text-sm font-medium text-stone-50 transition hover:opacity-90"
  >
    Open Command Palette
  </button>
  <div
    data-slot="dialog-overlay"
    class="fixed inset-0 z-50 bg-black/45 opacity-0 transition-opacity duration-200 data-[open]:opacity-100 data-[starting-style]:opacity-0 data-[ending-style]:opacity-0"
    hidden
  ></div>
  <div
    data-slot="dialog-content"
    class="fixed left-1/2 top-[16%] z-[60] w-[calc(100%-2rem)] max-w-xl -translate-x-1/2 rounded-[1.2rem] border border-stone-300 bg-[#f8f3ec] p-4 opacity-0 shadow-[0_24px_60px_rgba(25,20,15,0.2)] transition-all duration-200 data-[open]:opacity-100 data-[starting-style]:translate-y-2 data-[starting-style]:opacity-0 data-[ending-style]:translate-y-2 data-[ending-style]:opacity-0"
    hidden
  >
    <h2 data-slot="dialog-title" class="sr-only">Command Palette</h2>
    <p data-slot="dialog-description" class="sr-only">Search for a command to run across your workspace.</p>

    <div
      data-slot="command"
      data-label="Global Command Palette"
      class="overflow-hidden rounded-[1rem] border border-stone-300 bg-[linear-gradient(180deg,#faf9f7_0%,#f2ece4_100%)]"
    >
      <div data-slot="command-input-wrapper" class="flex items-center gap-2 border-b border-stone-300/60 px-4 py-3">
        <span class="text-sm text-stone-500"></span>
        <input
          data-slot="command-input"
          class="w-full border-none bg-transparent text-[0.95rem] text-stone-950 outline-none placeholder:text-stone-400"
          placeholder="Search commands..."
        />
      </div>
      <div data-slot="command-list" class="max-h-72 overflow-auto p-1.5">
        <div data-slot="command-empty" class="px-3 py-4 text-center text-sm text-stone-500" hidden>No results.</div>
        <div
          data-slot="command-item"
          class="flex cursor-default items-center gap-3 rounded-md px-3 py-2.5 text-sm text-stone-950 data-[selected]:bg-stone-200"
        >
          New Issue
          <span data-slot="command-shortcut" class="ml-auto text-[0.72rem] uppercase tracking-[0.08em] text-stone-500">⌘N</span>
        </div>
        <div
          data-slot="command-item"
          class="flex cursor-default items-center gap-3 rounded-md px-3 py-2.5 text-sm text-stone-950 data-[selected]:bg-stone-200"
        >
          Jump to Inbox
          <span data-slot="command-shortcut" class="ml-auto text-[0.72rem] uppercase tracking-[0.08em] text-stone-500">G I</span>
        </div>
        <div
          data-slot="command-item"
          class="flex cursor-default items-center gap-3 rounded-md px-3 py-2.5 text-sm text-stone-950 data-[selected]:bg-stone-200"
        >
          Open Settings
          <span data-slot="command-shortcut" class="ml-auto text-[0.72rem] uppercase tracking-[0.08em] text-stone-500">⌘,</span>
        </div>
      </div>
    </div>
  </div>
</div>

Slider

↓ Basic Slider
View source
<div data-slot="slider" data-default-value="50" class="slider">
  <div data-slot="slider-track" class="slider-track">
    <div data-slot="slider-range" class="slider-range"></div>
  </div>
  <div data-slot="slider-thumb" class="slider-thumb"></div>
</div>

<style>
  .slider {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
    height: 1.25rem;
  }
  .slider-track {
    width: 100%;
    height: 4px;
    background: #e5e5e5;
    border-radius: 2px;
  }
  .slider-range {
    background: #1a1a1a;
    border-radius: 2px;
  }
  .slider-thumb {
    width: 1.25rem;
    height: 1.25rem;
    background: #fff;
    border: 2px solid #1a1a1a;
    border-radius: 50%;
    cursor: grab;
  }
  .slider-thumb:focus {
    outline: none;
    box-shadow: 0 0 0 3px rgba(26, 26, 26, 0.2);
  }
  .slider-thumb[data-dragging] { cursor: grabbing; }
  .slider[data-disabled] .slider-thumb {
    cursor: not-allowed;
    opacity: 0.5;
  }
</style>
<div data-slot="slider" data-default-value="50" class="relative flex h-5 w-full items-center">
  <div data-slot="slider-track" class="h-1 w-full rounded bg-gray-200">
    <div data-slot="slider-range" class="rounded bg-gray-900"></div>
  </div>
  <div
    data-slot="slider-thumb"
    class="h-5 w-5 rounded-full border-2 border-gray-900 bg-white cursor-grab focus:outline-none focus:ring-2
           focus:ring-gray-900/20 data-[dragging]:cursor-grabbing"
  ></div>
</div>
↓ Range Slider
View source
<div data-slot="slider" data-default-value="25,75" class="slider">
  <div data-slot="slider-track" class="slider-track">
    <div data-slot="slider-range" class="slider-range"></div>
  </div>
  <div data-slot="slider-thumb" class="slider-thumb"></div>
  <div data-slot="slider-thumb" class="slider-thumb"></div>
</div>

<style>
  /* Same styles as basic slider */
</style>
<div data-slot="slider" data-default-value="25,75" class="relative flex h-5 w-full items-center">
  <div data-slot="slider-track" class="h-1 w-full rounded bg-gray-200">
    <div data-slot="slider-range" class="rounded bg-gray-900"></div>
  </div>
  <div
    data-slot="slider-thumb"
    class="h-5 w-5 rounded-full border-2 border-gray-900 bg-white cursor-grab focus:outline-none focus:ring-2
           focus:ring-gray-900/20 data-[dragging]:cursor-grabbing"
  ></div>
  <div
    data-slot="slider-thumb"
    class="h-5 w-5 rounded-full border-2 border-gray-900 bg-white cursor-grab focus:outline-none focus:ring-2
           focus:ring-gray-900/20 data-[dragging]:cursor-grabbing"
  ></div>
</div>

Switch

↓ interactive
View source
<div class="switch-stack">
  <label class="switch-row">
    <span data-slot="switch" data-default-checked class="switch-root">
      <span data-slot="switch-thumb" class="switch-thumb"></span>
    </span>
    Release updates
  </label>

  <label class="switch-row">
    <span data-slot="switch" class="switch-root switch-root--compact">
      <span data-slot="switch-thumb" class="switch-thumb switch-thumb--compact"></span>
    </span>
    Compact alerts
  </label>
</div>

<style>
  .switch-stack {
    display: grid;
    gap: 0.875rem;
  }

  .switch-row {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    color: #1f2937;
  }

  .switch-root {
    position: relative;
    display: inline-flex;
    width: 32px;
    height: 18px;
    align-items: center;
    border-radius: 999px;
    background: #d1d5db;
    padding: 1px;
    transition: background-color 150ms ease;
    outline: none;
  }

  .switch-root[data-checked] {
    background: #111827;
  }

  .switch-root--compact {
    width: 24px;
    height: 14px;
  }

  .switch-thumb {
    display: block;
    width: 16px;
    height: 16px;
    border-radius: 999px;
    background: white;
    box-shadow: 0 1px 2px rgb(15 23 42 / 0.18);
    transition: transform 150ms ease;
  }

  .switch-thumb[data-checked] {
    transform: translateX(14px);
  }

  .switch-thumb--compact {
    width: 12px;
    height: 12px;
  }

  .switch-root--compact .switch-thumb[data-checked] {
    transform: translateX(10px);
  }
</style>
<div class="grid gap-3">
  <label class="inline-flex items-center gap-3 text-sm text-gray-900">
    <span
      data-slot="switch"
      data-default-checked
      data-size="default"
      class="data-checked:bg-gray-900 data-unchecked:bg-gray-300 focus-visible:ring-gray-400/70 group/switch relative inline-flex h-[18px] w-[32px] shrink-0 items-center rounded-full border border-transparent p-px outline-none transition-colors focus-visible:ring-2"
    >
      <span
        data-slot="switch-thumb"
        class="pointer-events-none block size-4 rounded-full bg-white shadow-sm transition-transform data-checked:translate-x-[14px] data-unchecked:translate-x-0"
      ></span>
    </span>
    Release updates
  </label>

  <label class="inline-flex items-center gap-3 text-sm text-gray-900">
    <span
      data-slot="switch"
      data-size="sm"
      class="data-checked:bg-gray-900 data-unchecked:bg-gray-300 focus-visible:ring-gray-400/70 group/switch relative inline-flex h-[14px] w-[24px] shrink-0 items-center rounded-full border border-transparent p-px outline-none transition-colors focus-visible:ring-2"
    >
      <span
        data-slot="switch-thumb"
        class="pointer-events-none block size-3 rounded-full bg-white shadow-sm transition-transform data-checked:translate-x-[10px] data-unchecked:translate-x-0"
      ></span>
    </span>
    Compact alerts
  </label>
</div>

Radio Group

↓ interactive
Choose a plan
Choose a plan
View source
<div class="radio-card">
  <div class="radio-heading" id="plan-css-label">Choose a plan</div>
  <div
    data-slot="radio-group"
    data-default-value="pro"
    data-name="plan"
    aria-labelledby="plan-css-label"
    class="radio-group"
  >
    <label class="radio-option">
      <span data-slot="radio-group-item" data-value="starter" class="radio-item">
        <span data-slot="radio-group-indicator" class="radio-indicator">
          <span class="radio-dot"></span>
        </span>
      </span>
      Starter
    </label>

    <label class="radio-option">
      <span data-slot="radio-group-item" data-value="pro" class="radio-item">
        <span data-slot="radio-group-indicator" class="radio-indicator">
          <span class="radio-dot"></span>
        </span>
      </span>
      Pro
    </label>

    <label class="radio-option radio-option--disabled">
      <span
        data-slot="radio-group-item"
        data-value="enterprise"
        data-disabled
        class="radio-item"
      >
        <span data-slot="radio-group-indicator" class="radio-indicator">
          <span class="radio-dot"></span>
        </span>
      </span>
      Enterprise
    </label>
  </div>
</div>

<style>
  .radio-card {
    display: grid;
    gap: 0.875rem;
    max-width: 18rem;
  }

  .radio-heading {
    font-size: 0.875rem;
    font-weight: 600;
    color: #111827;
  }

  .radio-group {
    display: grid;
    gap: 0.625rem;
    width: 100%;
  }

  .radio-option {
    display: inline-flex;
    align-items: center;
    gap: 0.75rem;
    color: #111827;
  }

  .radio-option--disabled {
    color: #9ca3af;
  }

  .radio-item {
    position: relative;
    display: inline-flex;
    width: 1rem;
    height: 1rem;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    border: 1px solid #d1d5db;
    border-radius: 999px;
    background: #fff;
    outline: none;
    transition: border-color 150ms ease, background-color 150ms ease;
  }

  .radio-item[data-checked] {
    background: #111827;
    border-color: #111827;
  }

  .radio-item[data-disabled] {
    opacity: 0.5;
    cursor: not-allowed;
  }

  .radio-item:focus-visible {
    box-shadow: 0 0 0 3px rgb(17 24 39 / 0.18);
  }

  .radio-indicator {
    display: inline-flex;
    width: 1rem;
    height: 1rem;
    align-items: center;
    justify-content: center;
  }

  .radio-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 999px;
    background: #f9fafb;
    opacity: 0;
    transform: scale(0.75);
    transition: opacity 150ms ease, transform 150ms ease;
  }

  .radio-indicator[data-checked] .radio-dot {
    opacity: 1;
    transform: scale(1);
  }
</style>
<div class="grid max-w-72 gap-3">
  <div id="plan-tw-label" class="text-sm font-semibold text-gray-900">Choose a plan</div>
  <div
    data-slot="radio-group"
    data-default-value="pro"
    data-name="plan"
    aria-labelledby="plan-tw-label"
    class="grid w-full gap-2"
  >
    <label class="inline-flex items-center gap-3 text-sm text-gray-900">
      <span
        data-slot="radio-group-item"
        data-value="starter"
        class="border-input data-checked:bg-gray-900 data-checked:border-gray-900 focus-visible:ring-gray-900/20 relative inline-flex size-4 shrink-0 rounded-full border bg-white outline-none focus-visible:ring-3"
      >
        <span
          data-slot="radio-group-indicator"
          class="data-unchecked:opacity-0 data-checked:opacity-100 inline-flex size-4 items-center justify-center transition-opacity"
        >
          <span class="size-2 rounded-full bg-gray-50"></span>
        </span>
      </span>
      Starter
    </label>

    <label class="inline-flex items-center gap-3 text-sm text-gray-900">
      <span
        data-slot="radio-group-item"
        data-value="pro"
        class="border-input data-checked:bg-gray-900 data-checked:border-gray-900 focus-visible:ring-gray-900/20 relative inline-flex size-4 shrink-0 rounded-full border bg-white outline-none focus-visible:ring-3"
      >
        <span
          data-slot="radio-group-indicator"
          class="data-unchecked:opacity-0 data-checked:opacity-100 inline-flex size-4 items-center justify-center transition-opacity"
        >
          <span class="size-2 rounded-full bg-gray-50"></span>
        </span>
      </span>
      Pro
    </label>

    <label class="inline-flex items-center gap-3 text-sm text-gray-400">
      <span
        data-slot="radio-group-item"
        data-value="enterprise"
        data-disabled
        class="border-input data-checked:bg-gray-900 data-checked:border-gray-900 relative inline-flex size-4 shrink-0 rounded-full border bg-white opacity-50 outline-none"
      >
        <span
          data-slot="radio-group-indicator"
          class="data-unchecked:opacity-0 data-checked:opacity-100 inline-flex size-4 items-center justify-center transition-opacity"
        >
          <span class="size-2 rounded-full bg-gray-50"></span>
        </span>
      </span>
      Enterprise
    </label>
  </div>
</div>

Toggle

↓ interactive
View source
<div class="toggle-group">
  <button data-slot="toggle" class="toggle-btn">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>
      <path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>
    </svg>
  </button>
  <button data-slot="toggle" class="toggle-btn">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <line x1="19" y1="4" x2="10" y2="4"/>
      <line x1="14" y1="20" x2="5" y2="20"/>
      <line x1="15" y1="4" x2="9" y2="20"/>
    </svg>
  </button>
  <button data-slot="toggle" data-default-pressed class="toggle-btn">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <path d="M6 4v16"/>
      <path d="M18 4v16"/>
      <path d="M6 12h12"/>
    </svg>
  </button>
</div>

<style>
  .toggle-group {
    display: flex;
    gap: 0.25rem;
  }
  .toggle-btn {
    padding: 0.5rem;
    background: transparent;
    border: 1px solid #ccc;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, border-color 0.15s;
  }
  .toggle-btn:hover {
    background: #f0eeeb;
  }
  .toggle-btn[data-state="on"] {
    background: #333;
    border-color: #333;
    color: white;
  }
</style>
<div class="flex gap-1">
  <button
    data-slot="toggle"
    class="p-2 bg-transparent border border-gray-300 cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-gray-800
           data-[state=on]:border-gray-800 data-[state=on]:text-white"
  >
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>
      <path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>
    </svg>
  </button>
  <button
    data-slot="toggle"
    class="p-2 bg-transparent border border-gray-300 cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-gray-800
           data-[state=on]:border-gray-800 data-[state=on]:text-white"
  >
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <line x1="19" y1="4" x2="10" y2="4"/>
      <line x1="14" y1="20" x2="5" y2="20"/>
      <line x1="15" y1="4" x2="9" y2="20"/>
    </svg>
  </button>
  <button
    data-slot="toggle"
    data-default-pressed
    class="p-2 bg-transparent border border-gray-300 cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-gray-800
           data-[state=on]:border-gray-800 data-[state=on]:text-white"
  >
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <path d="M6 4v16"/>
      <path d="M18 4v16"/>
      <path d="M6 12h12"/>
    </svg>
  </button>
</div>

Toggle Group

↓ interactive
View source
<!-- Single selection (alignment) -->
<div data-slot="toggle-group" data-default-value="center" class="toggle-group">
  <button data-slot="toggle-group-item" data-value="left" class="toggle-group-btn" aria-label="Align left">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
      <line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="15" y2="12"/><line x1="3" y1="18" x2="18" y2="18"/>
    </svg>
  </button>
  <button data-slot="toggle-group-item" data-value="center" class="toggle-group-btn" aria-label="Align center">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
      <line x1="3" y1="6" x2="21" y2="6"/><line x1="6" y1="12" x2="18" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/>
    </svg>
  </button>
  <button data-slot="toggle-group-item" data-value="right" class="toggle-group-btn" aria-label="Align right">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
      <line x1="3" y1="6" x2="21" y2="6"/><line x1="9" y1="12" x2="21" y2="12"/><line x1="6" y1="18" x2="21" y2="18"/>
    </svg>
  </button>
</div>

<!-- Multiple selection (text formatting) -->
<div data-slot="toggle-group" data-multiple data-default-value="bold" class="toggle-group" aria-label="Text formatting">
  <button data-slot="toggle-group-item" data-value="bold" class="toggle-group-btn" aria-label="Bold">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>
    </svg>
  </button>
  <button data-slot="toggle-group-item" data-value="italic" class="toggle-group-btn" aria-label="Italic">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/>
    </svg>
  </button>
  <button data-slot="toggle-group-item" data-value="underline" class="toggle-group-btn" aria-label="Underline">
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <path d="M6 4v6a6 6 0 0 0 12 0V4"/><line x1="4" y1="20" x2="20" y2="20"/>
    </svg>
  </button>
</div>

<style>
  .toggle-group {
    display: inline-flex;
    gap: 0.25rem;
    margin-bottom: 0.75rem;
  }
  .toggle-group-btn {
    padding: 0.5rem;
    background: transparent;
    border: 1px solid #ccc;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, border-color 0.15s;
  }
  .toggle-group-btn:hover {
    background: #f0eeeb;
  }
  .toggle-group-btn[data-state="on"] {
    background: #333;
    border-color: #333;
    color: white;
  }
</style>
<!-- Single selection (alignment) -->
<div data-slot="toggle-group" data-default-value="center" class="inline-flex gap-1 mb-3">
  <button
    data-slot="toggle-group-item"
    data-value="left"
    aria-label="Align left"
    class="p-2 bg-transparent border border-gray-300 cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-gray-800
           data-[state=on]:border-gray-800 data-[state=on]:text-white"
  >
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
      <line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="15" y2="12"/><line x1="3" y1="18" x2="18" y2="18"/>
    </svg>
  </button>
  <button
    data-slot="toggle-group-item"
    data-value="center"
    aria-label="Align center"
    class="p-2 bg-transparent border border-gray-300 cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-gray-800
           data-[state=on]:border-gray-800 data-[state=on]:text-white"
  >
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
      <line x1="3" y1="6" x2="21" y2="6"/><line x1="6" y1="12" x2="18" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/>
    </svg>
  </button>
  <button
    data-slot="toggle-group-item"
    data-value="right"
    aria-label="Align right"
    class="p-2 bg-transparent border border-gray-300 cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-gray-800
           data-[state=on]:border-gray-800 data-[state=on]:text-white"
  >
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
      <line x1="3" y1="6" x2="21" y2="6"/><line x1="9" y1="12" x2="21" y2="12"/><line x1="6" y1="18" x2="21" y2="18"/>
    </svg>
  </button>
</div>

<!-- Multiple selection (text formatting) -->
<div data-slot="toggle-group" data-multiple data-default-value="bold" class="inline-flex gap-1" aria-label="Text formatting">
  <button
    data-slot="toggle-group-item"
    data-value="bold"
    aria-label="Bold"
    class="p-2 bg-transparent border border-gray-300 cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-gray-800
           data-[state=on]:border-gray-800 data-[state=on]:text-white"
  >
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>
    </svg>
  </button>
  <button
    data-slot="toggle-group-item"
    data-value="italic"
    aria-label="Italic"
    class="p-2 bg-transparent border border-gray-300 cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-gray-800
           data-[state=on]:border-gray-800 data-[state=on]:text-white"
  >
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/>
    </svg>
  </button>
  <button
    data-slot="toggle-group-item"
    data-value="underline"
    aria-label="Underline"
    class="p-2 bg-transparent border border-gray-300 cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-gray-800
           data-[state=on]:border-gray-800 data-[state=on]:text-white"
  >
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
      <path d="M6 4v6a6 6 0 0 0 12 0V4"/><line x1="4" y1="20" x2="20" y2="20"/>
    </svg>
  </button>
</div>
~~~

STYLING

Components expose data-state, switch-specific checked hooks, and ARIA attributes for CSS hooks:

/* State-based styling */
[data-state="active"]   { ... }
[data-state="open"]     { ... }
[data-checked]         { ... }
[data-unchecked]       { ... }
[aria-expanded="true"] { ... }
[aria-selected="true"] { ... }

/* With Tailwind */
<button class="aria-selected:font-bold">Tab</button>
<span class="data-checked:bg-black data-unchecked:bg-gray-300">Switch</span>

API

All components follow the same pattern:

// Auto-bind all instances
import { create } from "@data-slot/[component]";
const controllers = create(scope?);

// Create for specific element
import { createDialog } from "@data-slot/dialog";
const dialog = createDialog(element, options?);

// Common controller methods
controller.destroy(); // cleanup listeners