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

Toggle Group

A set of toggle buttons supporting single or multiple selection.

@data-slot/toggle-groupSource ↗
bun add @data-slot/toggle-group
npm install @data-slot/toggle-group
pnpm add @data-slot/toggle-group

Anatomy

Each item needs a data-value. Add data-multiple for a group where several buttons can be pressed at once.

<!-- Single selection (default) -->
<div data-slot="toggle-group" data-default-value="center">
  <button data-slot="toggle-group-item" data-value="left">Left</button>
  <button data-slot="toggle-group-item" data-value="center">Center</button>
  <button data-slot="toggle-group-item" data-value="right">Right</button>
</div>

<!-- Multiple selection -->
<div data-slot="toggle-group" data-multiple data-default-value="bold italic">
  <button data-slot="toggle-group-item" data-value="bold">B</button>
  <button data-slot="toggle-group-item" data-value="italic">I</button>
  <button data-slot="toggle-group-item" data-value="underline">U</button>
</div>

Examples

Pick one of several

Preview
Show codeHide code
<!-- 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;
  }
  .toggle-group-btn {
    padding: 0.5rem;
    background: transparent;
    border: 1px solid var(--border);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, border-color 0.15s;
  }
  .toggle-group-btn:hover {
    background: var(--code-bg);
  }
  .toggle-group-btn[data-state="on"] {
    background: var(--text);
    border-color: var(--text);
    color: var(--surface);
  }
  .toggle-groups { display: flex; flex-wrap: wrap; gap: 0.75rem; }
</style>
<!-- Single selection (alignment) -->
<div data-slot="toggle-group" data-default-value="center" class="inline-flex gap-1">
  <button
    data-slot="toggle-group-item"
    data-value="left"
    aria-label="Align left"
    class="p-2 bg-transparent border border-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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>

Disabled state

data-disabled on the root disables every button in the group.

Preview
Show codeHide code
<!-- Single selection (alignment) -->
<div data-slot="toggle-group" data-disabled 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-disabled 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;
  }
  .toggle-group-btn {
    padding: 0.5rem;
    background: transparent;
    border: 1px solid var(--border);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, border-color 0.15s;
  }
  .toggle-group-btn:hover {
    background: var(--code-bg);
  }
  .toggle-group-btn[data-state="on"] {
    background: var(--text);
    border-color: var(--text);
    color: var(--surface);
  }
  .toggle-groups { display: flex; flex-wrap: wrap; gap: 0.75rem; }
</style>
<!-- Single selection (alignment) -->
<div data-slot="toggle-group" data-disabled data-default-value="center" class="inline-flex gap-1">
  <button
    data-slot="toggle-group-item"
    data-value="left"
    aria-label="Align left"
    class="p-2 bg-transparent border border-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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-disabled 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-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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-[var(--border)] cursor-pointer
           flex items-center justify-center transition-colors
           hover:bg-code-bg data-[state=on]:bg-[var(--text)]
           data-[state=on]:border-[var(--text)] data-[state=on]:text-[var(--surface)]"
  >
    <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>

API reference

Initialization

create(scope?)

Find and bind uninitialized [data-slot="toggle-group"] descendants of scope (defaults to document). Returns ToggleGroupController[] for newly bound roots. To initialize the scope element itself, use createToggleGroup.

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

const controllers = create();

createToggleGroup(root, options?)

Create a ToggleGroupController for one root element. JavaScript options take precedence over the corresponding data attributes. Calling this again for a bound root returns its existing controller; destroy it before rebinding with new options.

import { createToggleGroup } from "@data-slot/toggle-group";

const controller = createToggleGroup(element, {});

Slots

Runtime Slots

  • toggle-group - Root element that manages single or multiple selection and keyboard navigation between items.
  • toggle-group-item - Toggle button inside the group, identified by a unique data-value; receives aria-pressed and data-state as its selection changes.

Data Attributes

Root Element

Attribute Description
data-slot="toggle-group" Required. Identifies the root element.
data-default-value Initial selected value(s). Space-separated for multiple values.
data-multiple Enable multiple selection mode.
data-orientation "horizontal" or "vertical" for keyboard navigation.
data-loop Wrap keyboard focus at the ends (default true); use "false" to stop at the ends.
data-disabled Disable the entire group.

Item Elements

Attribute Description
data-slot="toggle-group-item" Required. Identifies an item.
data-value Required. The value associated with this item.
data-disabled Disable this specific item.

State Attributes (set by component)

Attribute Values Description
aria-pressed "true" | "false" Whether item is pressed.
data-state "on" | "off" Visual state for styling.
data-value (on root) Space-separated values Current selection.

Options

Option Type Default Description
defaultValue string | string[] [] Initial selected value(s). For multiple values, use array or space-separated string.
multiple boolean false Allow multiple selections.
orientation "horizontal" | "vertical" "horizontal" Orientation for keyboard navigation.
loop boolean true Wrap keyboard focus at ends.
disabled boolean false Disable the entire group.
onValueChange (value: string[]) => void - Callback when selection changes.

Controller

Method/Property Description
setValue(value: string | string[]) Replace the selection. Strings are space-separated; unknown values are ignored and single mode keeps the first matching value.
toggle(value: string) Toggle one value; single mode clears the previous selection.
value Current selection (readonly string[], including in single mode).
destroy() Remove listeners and release the root binding.

Controller methods work while the group is disabled. User interaction and toggle-group:set are blocked while disabled.

Events

Outbound Events

// Listen for changes
root.addEventListener("toggle-group:change", (e) => {
  console.log(e.detail.value); // string[]
});

Inbound Events

Event Detail Description
toggle-group:set { value: string | string[] } Set selection programmatically
// Set selection from outside
root.dispatchEvent(new CustomEvent("toggle-group:set", {
  detail: { value: "bold" }
}));

// Set multiple values
root.dispatchEvent(new CustomEvent("toggle-group:set", {
  detail: { value: ["bold", "italic"] }
}));

Note: Blocked when group is disabled.

Deprecated Shapes

The following shapes are deprecated and will be removed in v1.0:

// Deprecated: bare string
root.dispatchEvent(new CustomEvent("toggle-group:set", {
  detail: "bold"
}));

// Deprecated: bare array
root.dispatchEvent(new CustomEvent("toggle-group:set", {
  detail: ["bold", "italic"]
}));

Use { value: ... } instead.

Styling

/* Style pressed items */
[data-slot="toggle-group-item"][data-state="on"] {
  background: #333;
  color: white;
}

/* Style disabled items */
[data-slot="toggle-group-item"][aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

Keyboard Navigation

Key Action
ArrowRight / ArrowDown Move to next item (based on orientation)
ArrowLeft / ArrowUp Move to previous item (based on orientation)
Home Move to first item
End Move to last item
Enter / Space Toggle current item

Accessibility

  • Root has role="group"
  • Items have aria-pressed attribute
  • Disabled items have aria-disabled="true" and native disabled
  • Supports roving tabindex for keyboard navigation
  • Add aria-label or aria-labelledby to root for context