Toggle
An on/off switch for binary settings and preferences. 48×28px, spring-animated thumb. Off is --ink-line; on is teal. Used in notification preferences, split defaults, and app settings. The spring motion gives it a physical, satisfying feel.
Variants
Standalone is used when the toggle is embedded in a larger component that already provides context — a card, a form group, a modal.
With label is the default for most settings — the label trails the switch. Label color transitions from muted (off) to ink (on) to reinforce the state change.
Settings row puts the label on the left and the toggle on the right, which maps to the iOS Settings pattern users already know. Use this in the app's settings screen.
Sizes
| Size | Track | Thumb | Travel | Use case |
|---|---|---|---|---|
| Default | 48×28px | 24px circle | 20px | Settings rows, preferences — most contexts |
| Compact | 40×24px | 20px circle | 16px | Dense forms, inline within cards |
States
| State | Track | Thumb position | Notes |
|---|---|---|---|
| Off | --ink-line | Left (2px from edge) | Default resting state |
| On | --teal | Right (translateX 20px) | Thumb springs to position |
| Disabled off | --ink-line at 40% opacity | Left | Cursor: not-allowed. Add aria-disabled="true". |
| Disabled on | --teal at 40% opacity | Right | Setting is locked on — explain why in surrounding text |
On dark surfaces
On dark surfaces the off-track uses --dark-border and the thumb uses --dark-text (paper). On state is unchanged — teal is the same. Label text uses --dark-text-2 (off) and --dark-text (on).
Anatomy
| Part | Element | Notes |
|---|---|---|
| Hidden input | .toggle-input (checkbox) | Visually hidden but present for native behavior, keyboard access, and form serialization. |
| Wrapper | <label class="toggle"> | Clicking anywhere on the label toggles the input. Set cursor: pointer. |
| Track | .toggle-track | 48×28px pill. Background transitions between --ink-line and --teal over 200ms spring. |
| Thumb | .toggle-thumb | 24px circle, paper white, shadow-sm. Translates 20px to the right when checked. |
| Label | .toggle-label | Optional. Can precede or follow the track depending on layout variant. |
Usage
Accessibility
<input type="checkbox"> under the hood — screen readers announce it as a switch. Add role="switch" to the input for semantic accuracy: <input type="checkbox" role="switch">. aria-checked is managed automatically.
Tab. Space toggles the state. Focus ring appears on the track via :focus-visible: 2px solid var(--teal), 2px offset. Never remove the focus ring.
disabled attribute — it removes focusability automatically. If the toggle is locked by a permission (not disabled by logic), use aria-disabled="true" and keep it focusable so screen reader users can encounter it. Include an explanation in the sublabel.
Code
HTML
<!-- Default toggle with label --> <label class="toggle"> <input class="toggle-input" type="checkbox" role="switch"> <div class="toggle-track"><div class="toggle-thumb"></div></div> <span class="toggle-label">Settle reminders</span> </label> <!-- Settings row --> <div class="toggle-row"> <div class="toggle-label-group"> <span class="toggle-label">GPay nudges</span> <span class="toggle-sublabel">Send UPI requests via GPay</span> </div> <label class="toggle"> <input class="toggle-input" type="checkbox" role="switch" checked> <div class="toggle-track"><div class="toggle-thumb"></div></div> </label> </div> <!-- Disabled --> <label class="toggle disabled"> <input class="toggle-input" type="checkbox" role="switch" disabled> <div class="toggle-track"><div class="toggle-thumb"></div></div> <span class="toggle-label">Locked setting</span> </label>
Design tokens used
| Token | Value | Role |
|---|---|---|
--ink-line | #DDD2B8 | Track — off state |
--teal | #0E7C66 | Track — on state |
--s-paper | #FBF8F0 | Thumb fill |
--shadow-sm | 0 1px 2px rgba(23,21,19,.06) | Thumb elevation |
--ease-spring | cubic-bezier(0.34,1.56,0.64,1) | Thumb travel + track color |
--r-pill | 999px | Track border radius |