@data-slot/toggle-group
A group of toggle buttons with shared state, supporting single or multiple selection.
Installation
bun add @data-slot/toggle-group
Usage
Basic Markup
<!-- 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>
JavaScript
import { create, createToggleGroup } from "@data-slot/toggle-group";
// Auto-discover and bind all toggle-group elements
const controllers = create();
// Or target a specific element
const group = createToggleGroup(element, {
defaultValue: "center",
multiple: false,
orientation: "horizontal",
loop: true,
onValueChange: (value) => console.log(value),
});
// Programmatic control
group.setValue("left");
group.toggle("bold");
console.log(group.value); // ["left"]
// Cleanup
group.destroy();
API
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