Toggle Group
A set of toggle buttons supporting single or multiple selection.
@data-slot/toggle-groupSource ↗bun add @data-slot/toggle-groupnpm install @data-slot/toggle-grouppnpm add @data-slot/toggle-groupAnatomy
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
Show 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>import { create } from "@data-slot/toggle-group";
// Initialize after the markup is in the document.
const controllers = create();
// Clean up before removing the component.
// controllers.forEach((controller) => controller.destroy());Disabled state
data-disabled on the root disables every button in the group.
Show 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>import { create } from "@data-slot/toggle-group";
// Initialize after the markup is in the document.
const controllers = create();
// Clean up before removing the component.
// controllers.forEach((controller) => controller.destroy());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 uniquedata-value; receivesaria-pressedanddata-stateas 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-pressedattribute - Disabled items have
aria-disabled="true"and nativedisabled - Supports roving tabindex for keyboard navigation
- Add
aria-labeloraria-labelledbyto root for context