Switch
A control that allows users to toggle between two states: on and off.
Demo
Accessibility Features
WAI-ARIA Roles
-
switch- An input widget that allows users to choose one of two values: on or off
WAI-ARIA switch role (opens in new tab)
WAI-ARIA States
aria-checked
Indicates the current checked state of the switch.
| Values | true | false |
| Required | Yes (for switch role) |
| Default | initialChecked prop (default: false) |
| Change Trigger | Click, Enter, Space |
| Reference | aria-checked (opens in new tab) |
aria-disabled
Indicates the switch is perceivable but disabled.
| Values | true | undefined |
| Required | No (only when disabled) |
| Reference | aria-disabled (opens in new tab) |
Keyboard Support
| Key | Action |
|---|---|
| Space | Toggle the switch state (on/off) |
| Enter | Toggle the switch state (on/off) |
Accessible Naming
Switches must have an accessible name. This can be provided through:
- Visible label (recommended) - The switch's child content provides the accessible name
-
aria-label- Provides an invisible label for the switch -
aria-labelledby- References an external element as the label
Visual Design
This implementation follows WCAG 1.4.1 (Use of Color) by not relying solely on color to indicate state:
- Thumb position - Left = off, Right = on
- Checkmark icon - Visible only when the switch is on
- Forced colors mode - Uses system colors for accessibility in Windows High Contrast Mode
Source Code
Switch.astro
---
/**
* APG Switch Pattern - Astro Implementation
*
* A control that allows users to toggle between two states: on and off.
* Uses Web Components for client-side interactivity.
*
* @see https://www.w3.org/WAI/ARIA/apg/patterns/switch/
*/
export interface Props {
/** Initial checked state */
initialChecked?: boolean;
/** Whether the switch is disabled */
disabled?: boolean;
/** Additional CSS class */
class?: string;
}
const {
initialChecked = false,
disabled = false,
class: className = "",
} = Astro.props;
---
<apg-switch class={className}>
<button
type="button"
role="switch"
class="apg-switch"
aria-checked={initialChecked}
aria-disabled={disabled || undefined}
disabled={disabled}
>
<span class="apg-switch-track">
<span class="apg-switch-icon" aria-hidden="true">
<svg viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.28 2.28a.75.75 0 00-1.06-1.06L4.5 5.94 2.78 4.22a.75.75 0 00-1.06 1.06l2.25 2.25a.75.75 0 001.06 0l5.25-5.25z"
fill="currentColor"
/>
</svg>
</span>
<span class="apg-switch-thumb"></span>
</span>
{Astro.slots.has('default') && (
<span class="apg-switch-label">
<slot />
</span>
)}
</button>
</apg-switch>
<script>
class ApgSwitch extends HTMLElement {
private button: HTMLButtonElement | null = null;
private rafId: number | null = null;
connectedCallback() {
this.rafId = requestAnimationFrame(() => this.initialize());
}
private initialize() {
this.rafId = null;
this.button = this.querySelector('button[role="switch"]');
if (!this.button) {
console.warn("apg-switch: button element not found");
return;
}
this.button.addEventListener("click", this.handleClick);
this.button.addEventListener("keydown", this.handleKeyDown);
}
disconnectedCallback() {
if (this.rafId !== null) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
this.button?.removeEventListener("click", this.handleClick);
this.button?.removeEventListener("keydown", this.handleKeyDown);
this.button = null;
}
private toggle() {
if (!this.button || this.button.disabled) return;
const currentChecked =
this.button.getAttribute("aria-checked") === "true";
const newChecked = !currentChecked;
this.button.setAttribute("aria-checked", String(newChecked));
this.dispatchEvent(
new CustomEvent("change", {
detail: { checked: newChecked },
bubbles: true,
})
);
}
private handleClick = () => {
this.toggle();
};
private handleKeyDown = (event: KeyboardEvent) => {
if (event.key === " " || event.key === "Enter") {
event.preventDefault();
this.toggle();
}
};
}
if (!customElements.get("apg-switch")) {
customElements.define("apg-switch", ApgSwitch);
}
</script> Usage
Example
---
import Switch from './Switch.astro';
---
<Switch initialChecked={false}>
Enable notifications
</Switch>
<script>
// Listen for change events
document.querySelector('apg-switch')?.addEventListener('change', (e) => {
console.log('Checked:', e.detail.checked);
});
</script> API
| Prop | Type | Default | Description |
|---|---|---|---|
initialChecked | boolean | false | Initial checked state |
disabled | boolean | false | Whether the switch is disabled |
class | string | "" | Additional CSS classes |
Custom Events
| Event | Detail | Description |
|---|---|---|
change | { checked: boolean } | Fired when the switch state changes |
Slots
| Slot | Description |
|---|---|
default | Switch label content |