Skip to content

Toggle

A two-state button that can be either on or off.

Updated View as Markdown

Toggle

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 a type is 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" and tabindex="0". The element’s native click event (from mouse or keyboard) toggles the state.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close