Toggle
A two-state button that can be either on or off.
A pressable button that toggles between pressed and unpressed states. Built on role="button" with aria-pressed, it emits change events and supports both button and non-button root elements.
import { Toggle } from "@areia/slots";
document.querySelector("#root")!.innerHTML = `
<button data-slot="toggle" class="win95-button data-[state=on]:border-t-black data-[state=on]:border-l-black data-[state=on]:border-r-white data-[state=on]:border-b-white">Bold</button>
`;
const root = document.querySelector<HTMLElement>(
'[data-slot="toggle"]',
)!;
const controller = Toggle.createToggle(root, {
defaultPressed: false,
});
Import
import { Toggle } from "@areia/slots";
Usage
import { Toggle } from "@areia/slots";
// Auto-bind a single root
const root = document.querySelector('[data-slot="toggle"]');
const controller = Toggle.createToggle(root, {
defaultPressed: false,
onPressedChange: (pressed) =>
console.log("pressed:", pressed),
});
// Listen for state changes
root.addEventListener("toggle:change", (e) => {
console.log(e.detail.pressed);
});
// Auto-bind all unbound roots in scope
const controllers = Toggle.create();
Expected Markup
The toggle is a single-element primitive — there are no sub-slots. It can be a native <button> or any other focusable element.
<!-- Native button (recommended) -->
<button data-slot="toggle">Bold</button>
<!-- Non-button element -->
<div data-slot="toggle" tabindex="0" role="button">Italic</div>
When the root is a native <button>, the controller sets type="button" (if not already set) to prevent accidental form submission. Button elements handle click and keyboard events natively.
Data Slots
| Slot | Required | Description |
|---|---|---|
toggle |
Yes | Root element. Receives aria-pressed and state. |
Generated Attributes
| Attribute | Element | Description |
|---|---|---|
data-state |
Root | "on" when pressed, "off" when not pressed. |
aria-pressed |
Root | "true" or "false". |
aria-disabled |
Root | "true" when disabled (on all element types). |
disabled |
Root | Native attribute set on <button> elements when disabled. |
type |
Root | Set to "button" on <button> elements to prevent form submission (unless already set). |
role |
Root | Set to "button" when the root is not already a <button>. |
Events
Outbound
| Event | Detail | Description |
|---|---|---|
toggle:change |
{ pressed: boolean } |
Fired on the root when the pressed state changes. |
Inbound
| Event | Detail | Description |
|---|---|---|
toggle:set |
{ value: boolean } (preferred), boolean (deprecated), or { pressed: boolean } (deprecated) |
Dispatch on the root to programmatically set the pressed state. Blocked when disabled. |
API Reference
Options
| Option | Type | Default | Description |
|---|---|---|---|
defaultPressed |
boolean |
false |
Initial pressed state. |
disabled |
boolean |
false |
Disable user interaction. |
onPressedChange |
(pressed: boolean) => void |
— | Called when the pressed state changes. |
Controller
| Member | Type | Description |
|---|---|---|
pressed |
boolean (getter) |
Current pressed state. |
toggle() |
() => void |
Toggle the pressed state. Always works, even when disabled. |
press() |
() => void |
Set pressed to true. Always works, even when disabled. |
release() |
() => void |
Set pressed to false. Always works, even when disabled. |
destroy() |
() => void |
Remove all listeners and clean up. |
Controller
The controller is returned by Toggle.createToggle() and provides imperative control over the pressed state.
const controller = Toggle.createToggle(root);
// Toggle
controller.toggle();
// Set pressed to true
controller.press();
// Set pressed to false
controller.release();
// Read current state
console.log(controller.pressed); // true | false
// Clean up
controller.destroy();
Disabled vs. Programmatic Control
User interaction (clicks) and inbound events (toggle:set) are blocked when the toggle is disabled. However, the controller methods (toggle(), press(), release()) always work regardless of disabled state. This allows you to have explicit programmatic control while still blocking user input.
Button vs. Non-Button Elements
- Button elements: The controller sets
type="button"to prevent form submission (unless atypeis already specified). Button elements handle keyboard interaction (Enter/Space) natively, so no additional keyboard handlers are added. - Non-button elements: The controller sets
role="button"andtabindex="0". The element’s nativeclickevent (from mouse or keyboard) toggles the state.