Toggle / Switch
Toggle switches allow users to turn a single setting on or off immediately. Unlike Checkbox which requires form submission, Toggle fires the action instantly on change.
When to Use
Use Toggle when
- Turning a feature on or off immediately (no save button)
- Settings pages — notifications, preferences, feature flags
- Binary state that takes effect instantly
Don't use Toggle when
- Selection in a form that requires submit — use Checkbox
- Choosing one option from multiple — use Radio Group
- Action needs confirmation before firing
Anatomy
1
Track
The pill-shaped background. Off: bg:#E2E8F0. On: bg:#6366F1. Width:44px, height:24px, border-radius:999px. Transition: background 0.2s ease.
2
Thumb
White circle that slides inside the track. 20px diameter. Translates 0 → 20px when toggled. box-shadow for elevation. Transition: transform 0.2s cubic-bezier(0.4,0,0.2,1).
3
Label (optional)
Text to the right of the toggle. 13px fw-500. Describes the setting being toggled. Always present for accessibility.
4
Native input
Hidden input[type=checkbox] underneath — retains all native accessibility, keyboard support, and form behavior.
Variants
Off (default)
On
With sub-label
Disabled
States
Off
bg:#E2E8F0, thumb left
On
bg:#6366F1, thumb right
Disabled On
opacity:.45, cursor:not-allowed
Disabled Off
opacity:.45, no interaction
Properties
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| checked | boolean | optional | false | Controlled on/off state. |
| onChange | function | required | — | Called immediately on toggle. Fires before any save action. (e: ChangeEvent) => void |
| disabled | boolean | optional | false | Prevents interaction. opacity:.45, cursor:not-allowed. |
| label | string | required | — | Visible label. Also used as aria-label for screen readers. |
| subLabel | string | optional | undefined | Secondary description below label. |
Behavior
| Trigger | Behavior |
|---|---|
| Click / tap | Instantly fires onChange with new value. Track background transitions (0.2s). Thumb translates (0.2s cubic-bezier). No confirmation needed. |
| Space key | Toggles when focused. Native checkbox behavior. |
| Disabled | No toggle, no hover, no animation. opacity:.45. |
| Loading | When saving in progress: disable toggle + show spinner in place of thumb. Re-enable after save completes. |
Accessibility
| Requirement | Implementation |
|---|---|
| Role | Use native input[type=checkbox] underneath — inherits role="switch" automatically in modern browsers, or add role="switch" explicitly. |
| aria-checked | "true" when on, "false" when off. Native checkbox handles this. |
| Label | Always wrap in <label> or use aria-labelledby pointing to visible label text. |
| Keyboard | Tab focuses, Space toggles. All native. |
| Touch target | Track is 44×24px — minimum. Wrap in <label> to extend hit area to include label text. |
Composition
Allowed
Settings page — list of toggles per category
Inside cards with label + sub-label
Inline in table rows (feature flags)
Forbidden
Inside forms that require Submit button
For choosing between multiple options (use Radio)
Without a visible label
Best Practices
Do
Clear label + sub-label. Immediate action understood.
Don't
No label — screen readers can't announce purpose.
Content Rules
Good label
Email notifications
Noun phrase. What is being toggled.
Bad label
Enable email notifications when you receive a new message
Too verbose. Labels max 4 words.
Good sub-label
Receive alerts about new activity
Explains what the setting does.
Bad sub-label
Toggle this to turn on or turn off
Describes the UI, not the feature.
AI Implementation Guide
For AI coding assistants: Toggle is built on a hidden input[type=checkbox] with a custom CSS track+thumb overlay. Always wrap in <label> for click area. Track: 44×24px pill, bg:#E2E8F0 (off) → bg:#6366F1 (on). Thumb: 20px white circle, translateX(0) → translateX(20px). Both transitions use 0.2s. Never use Toggle inside a form that has a Save button — use Checkbox instead. Toggle fires onChange immediately.
Related Components
Design Decisions
Why custom CSS instead of input[type=checkbox] with appearance:none?
The hidden native checkbox approach retains all accessibility benefits (keyboard, screen reader, form submit) while allowing full visual customization via the CSS overlay. appearance:none on the input itself has inconsistent cross-browser behavior for the toggle shape.
Why #6366F1 (indigo) for the on state?
Consistent with the primary accent color system. The on state signals "active/enabled" and indigo is the primary interactive color across buttons, focus rings, and active states in the design system.
Changelog
v1.0.0July 2026Added
- Initial release — on/off, disabled states
- Built on hidden native checkbox for full accessibility
- 0.2s thumb slide + track color transition