Loading Developer Playground

Loading ...

Skip to main content
ARIA ROLEWidget Roles

switch

A type of checkbox that represents on/off values, as opposed to checked/unchecked values. Switches have immediate effect when toggled, unlike checkboxes which typically require form submission.

Key Attribute
aria-checked (true/false)
Superclass Role
checkbox
Toggle Keys
Space, Enter

Overview

The switch role is a specialized form of checkbox that represents an on/off toggle with immediate effect. Unlike checkboxes, switches don't support a mixed/indeterminate state - they are either on (aria-checked="true") or off (aria-checked="false").

Switches are commonly used for settings that take effect immediately, such as enabling/disabling features, toggling dark mode, or controlling notifications.

Switch vs Checkbox

Use a switch when the action takes immediate effect (like toggling dark mode). Use a checkbox when the selection will be submitted later (like agreeing to terms). Switches communicate "on/off" while checkboxes communicate "checked/unchecked".

Live Demo: Switch Controls

Dark Mode

Use dark theme throughout the app

Notifications

Receive push notifications

Auto-save

Save your work automatically

Wi-Fi

Connected

Keyboard support: Focus any switch, then press Space or Enter to toggle. Use Tab to move between switches.

Code Examples

Basic Switch

<!-- Basic Switch -->
<div class="switch-container">
  <span id="wifi-label">Wi-Fi</span>
  <button
    role="switch"
    aria-checked="true"
    aria-labelledby="wifi-label"
  >
    <span class="switch-track">
      <span class="switch-thumb"></span>
    </span>
  </button>
</div>

<!-- aria-checked="true" means ON
     aria-checked="false" means OFF -->

Switch with Button Element

<!-- Switch Using Native Button -->
<button
  role="switch"
  aria-checked="false"
  aria-label="Enable notifications"
  class="switch"
>
  <span class="switch-track">
    <span class="switch-thumb"></span>
  </span>
  <span class="switch-label">Notifications</span>
</button>

<script>
  const switchButton = document.querySelector('[role="switch"]');
  
  switchButton.addEventListener('click', () => {
    const isChecked = switchButton.getAttribute('aria-checked') === 'true';
    switchButton.setAttribute('aria-checked', !isChecked);
  });
  
  // Keyboard support is automatic with <button>
</script>

<style>
  .switch {
    display: flex;
    align-items: center;
    gap: 12px;
    cursor: pointer;
    background: none;
    border: none;
    padding: 0;
  }

  .switch-track {
    width: 48px;
    height: 28px;
    background: #ccc;
    border-radius: 14px;
    position: relative;
    transition: background 0.2s;
  }

  [aria-checked="true"] .switch-track {
    background: #4f46e5;
  }

  .switch-thumb {
    width: 24px;
    height: 24px;
    background: white;
    border-radius: 50%;
    position: absolute;
    top: 2px;
    left: 2px;
    transition: left 0.2s;
  }

  [aria-checked="true"] .switch-thumb {
    left: 22px;
  }
</style>

Switch vs Checkbox

<!-- Switch vs Checkbox: When to Use Each -->

<!-- Use CHECKBOX when:
     - Selecting multiple independent options
     - Form submission with on/off values
     - The state isn't immediately applied -->
<label>
  <input type="checkbox" name="terms" />
  I agree to the terms
</label>

<!-- Use SWITCH when:
     - The setting takes effect immediately
     - It's a binary on/off toggle
     - It controls a system/app state -->
<button role="switch" aria-checked="true" aria-label="Dark mode">
  <span class="switch-track">
    <span class="switch-thumb"></span>
  </span>
</button>

<!-- Key difference: 
     Switches have IMMEDIATE effect
     Checkboxes typically require form submission -->

Switch with Description

<!-- Switch with Description -->
<div class="setting-row">
  <div class="setting-info">
    <span id="autosave-label" class="setting-name">Auto-save</span>
    <span id="autosave-desc" class="setting-description">
      Automatically save your work every 5 minutes
    </span>
  </div>
  
  <button
    role="switch"
    aria-checked="false"
    aria-labelledby="autosave-label"
    aria-describedby="autosave-desc"
    class="switch"
  >
    <span class="switch-track">
      <span class="switch-thumb"></span>
    </span>
  </button>
</div>

<!-- aria-describedby provides additional context
     for screen reader users -->

Grouped Switches

<!-- Group of Related Switches -->
<fieldset>
  <legend>Notification Settings</legend>
  
  <div class="switch-group">
    <div class="switch-row">
      <label id="email-label">Email notifications</label>
      <button
        role="switch"
        aria-checked="true"
        aria-labelledby="email-label"
      ></button>
    </div>
    
    <div class="switch-row">
      <label id="push-label">Push notifications</label>
      <button
        role="switch"
        aria-checked="false"
        aria-labelledby="push-label"
      ></button>
    </div>
    
    <div class="switch-row">
      <label id="sms-label">SMS notifications</label>
      <button
        role="switch"
        aria-checked="false"
        aria-labelledby="sms-label"
      ></button>
    </div>
  </div>
</fieldset>

<!-- Use fieldset/legend to group related switches -->

React Component

// React Switch Component
import { useState, useCallback } from 'react';

interface SwitchProps {
  label: string;
  description?: string;
  checked: boolean;
  onChange: (checked: boolean) => void;
  disabled?: boolean;
}

function Switch({
  label,
  description,
  checked,
  onChange,
  disabled = false,
}: SwitchProps) {
  const handleClick = useCallback(() => {
    if (!disabled) {
      onChange(!checked);
    }
  }, [checked, disabled, onChange]);

  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
    if (e.key === ' ' || e.key === 'Enter') {
      e.preventDefault();
      handleClick();
    }
  }, [handleClick]);

  const labelId = `switch-label-${label.toLowerCase().replace(/\s+/g, '-')}`;
  const descId = description 
    ? `switch-desc-${label.toLowerCase().replace(/\s+/g, '-')}`
    : undefined;

  return (
    <div className="switch-container">
      <div className="switch-info">
        <span id={labelId} className="switch-label">{label}</span>
        {description && (
          <span id={descId} className="switch-description">
            {description}
          </span>
        )}
      </div>
      
      <button
        type="button"
        role="switch"
        aria-checked={checked}
        aria-labelledby={labelId}
        aria-describedby={descId}
        aria-disabled={disabled}
        onClick={handleClick}
        onKeyDown={handleKeyDown}
        className={`switch ${checked ? 'checked' : ''} ${disabled ? 'disabled' : ''}`}
      >
        <span className="switch-track">
          <span className="switch-thumb" />
        </span>
      </button>
    </div>
  );
}

// Usage
function Settings() {
  const [darkMode, setDarkMode] = useState(false);
  const [notifications, setNotifications] = useState(true);

  return (
    <div className="settings">
      <Switch
        label="Dark Mode"
        description="Use dark theme throughout the app"
        checked={darkMode}
        onChange={setDarkMode}
      />
      
      <Switch
        label="Notifications"
        description="Receive push notifications"
        checked={notifications}
        onChange={setNotifications}
      />
    </div>
  );
}

Disabled Switch

<!-- Disabled Switch -->
<div class="switch-container">
  <span id="premium-label">Premium features</span>
  <span id="premium-desc" class="subdued">
    Upgrade to enable premium features
  </span>
  
  <button
    role="switch"
    aria-checked="false"
    aria-labelledby="premium-label"
    aria-describedby="premium-desc"
    aria-disabled="true"
    disabled
    class="switch disabled"
  >
    <span class="switch-track">
      <span class="switch-thumb"></span>
    </span>
  </button>
</div>

<style>
  .switch.disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
  
  .switch.disabled .switch-track {
    background: #ddd;
  }
</style>

<!-- Use both aria-disabled and native disabled
     for complete accessibility -->

Keyboard Support

Space
Toggles the switch on/off

Primary toggle key

Enter
Toggles the switch on/off

Alternative toggle key (when using button element)

Tab
Moves focus to the next focusable element

Standard navigation

Shift+Tab
Moves focus to the previous focusable element

Reverse navigation

Best Practices

Use switches for settings that take immediate effect

Use a <button> element as the base for automatic keyboard support

Provide clear labels that describe what the switch controls

Include aria-describedby for additional context when helpful

Ensure sufficient color contrast between on/off states

Consider adding icons or text to indicate on/off state for colorblind users

×

Don't use switches when the action requires form submission

×

Don't use aria-checked="mixed" - switches are binary only

×

Don't rely solely on color to indicate state

×

Don't use a switch when multiple related options exist - use checkboxes

Supported ARIA Attributes

aria-checkedRequired

Required. "true" for on, "false" for off (no "mixed")

aria-label

Accessible name for the switch

aria-labelledby

References element(s) that label the switch

aria-describedby

References additional description

aria-disabled

Indicates if the switch is disabled

aria-readonly

Indicates if the switch is read-only

Common Use Cases

Dark mode/theme toggle
Notification settings
Wi-Fi/Bluetooth on/off
Airplane mode toggle
Auto-save settings
Privacy/security toggles
Feature flags in settings
Sound/vibration toggles

Accessibility Notes

Screen Reader Announcements

Screen readers announce switches as "[label], switch, [on/off]". When toggled, the new state is announced. NVDA and JAWS specifically announce "on" or "off" rather than "checked" or "not checked" used for checkboxes.

Visual State Indication

Don't rely solely on color to indicate state. The switch thumb position provides a clear visual cue. Consider adding icons (like ✓/✗) or text labels (ON/OFF) to further clarify state for users with color vision deficiencies.

Immediate vs Deferred Actions

Switches should have immediate effect. If your toggle requires a "Save" action, use a checkbox instead. The switch pattern sets user expectations that the change happens instantly.

Related Roles & Attributes

Specifications & Resources