# Switch
> **Scope note** — This CRD describes the Switch component for **Acme**, a fictional product used as an illustrative example in the XConf Americas 2026 talk "Your Design System Has a New User: AI." It is not an authoritative component requirement for jeensdesign.com. Visual specifications reference tokens from the real portfolio design system (`src/styles/tokens.css`) as a baseline; proposed Acme-specific tokens are marked `[proposed]` and do not exist in the repository.
>
> Related controls: [Checkbox](./checkbox.crd.md) · [Radio Group](./radio-group.crd.md)
---
## Overview
A switch is a binary control that takes effect immediately when toggled — without a save button or confirmation step. It communicates that the change is instant and reversible. When a user toggles a switch, the setting updates at that moment.
Switches are distinct from checkboxes not just visually but semantically: the toggle metaphor implies immediacy. Placing a switch inside a form that requires submission creates a contradiction between the control's implied behavior and the page's actual interaction model.
---
## Best practices
Use a switch when a setting applies on change, with no confirmation step required.
**Use switches for:**
- Notification preferences (email, SMS, push, marketing)
- Privacy settings and data-sharing toggles
- Feature flags a user enables for their account
- Automatic updates or sync settings
- Any binary preference that takes effect on toggle
**Avoid switches when:**
- The setting is collected alongside others and submitted together (use a checkbox)
- The change has significant, hard-to-reverse consequences that warrant a confirmation dialog (use a button with a confirmation pattern instead)
- An explicit save or submit action follows the user's choices
**Labeling:** The label should describe the setting, not the toggle state. "Email notifications" is correct. "Enable email" or "On/Off" alone is not. The current state must be clear from the visual position of the thumb — not from the label alone.
**Notification preferences example:** A notification preferences screen that applies settings immediately — no save button — uses one switch per notification type. Each is labeled with the notification type; supporting text explains what the notification covers. This is a direct application of the guidance above.
---
## Anatomy
```
┌──────────────────────────────────┐
│ Label text [○──────] │ ← off state
│ Supporting text │ ← optional
│ │
│ Label text [──────●] │ ← on state
└──────────────────────────────────┘
```
| Part | Required | Description |
|------|----------|-------------|
| Track | Yes | 46×26px pill-shaped background. Communicates on/off state via fill color. |
| Thumb | Yes | 20×20px circle, inset 3px from the track edge. Slides left (off) or right (on). |
| Visible label | Yes | Text describing the setting. In Acme, placed to the left of the switch track. |
| Supporting text | No | Secondary description below the label, sharing the label's left edge. |
| Validation message | No | Appears below supporting text in the error state. Associated via `aria-describedby`. |
**Structural rules:**
- The label is to the left of the switch track in Acme's notification and settings contexts. Label placement must be consistent within a group.
- The interactive target covers the full label + switch row, minimum 44px tall.
- The thumb must be fully visible in both the on and off positions.
- Supporting text and validation messages are indented to the label's left edge.
---
## Visual specifications
| Property | Value | Token |
|----------|-------|-------|
| Track width | 46px | `--acme-switch-track-width` [proposed] |
| Track height | 26px | `--acme-switch-track-height` [proposed] |
| Track corner radius | 9999px | `--radius-full` (portfolio) |
| Thumb diameter | 20px | `--acme-switch-thumb-size` [proposed] |
| Thumb inset from track edge | 3px | `--acme-switch-thumb-inset` [proposed] |
| Control-to-label gap | 8px | `--space-2` (portfolio) |
| Supporting text top spacing | 4px | `--space-1` (portfolio) |
| Minimum touch target height | 44px | `--acme-control-touch-target` [proposed] |
| Transition duration | 150ms | `--duration-fast` (portfolio) |
| Transition easing | cubic-bezier(0.22, 1, 0.36, 1) | `--ease-out` (portfolio) |
| Label font | DM Sans | `--font-body` (portfolio) |
| Label font size | 14px | `--text-sm` (portfolio) |
| Label font weight | 500 | — |
| Supporting text font size | 12px | `--text-xs` (portfolio) |
---
## Token mapping
| Element | Property | Acme token [proposed] | Maps to (portfolio) | Resolved value | State |
|---------|----------|----------------------|---------------------|----------------|-------|
| Track | Background | `--acme-switch-track-off` | `--color-gray-200` | #E8E8E0 | Off |
| Track | Background | `--acme-switch-track-on` | `--color-black` | #0A0A0A | On |
| Track | Background | `--acme-switch-track-disabled` | `--color-gray-100` | #F5F5F0 | Disabled |
| Track | Border | — | none | — | Default |
| Track | Border | `--acme-control-border-error` | `--color-error` | #FF4444 | Error |
| Thumb | Background | `--acme-switch-thumb-off` | `--color-white` | #FAFAFA | Off |
| Thumb | Background | `--acme-switch-thumb-on` | `--color-yellow` | #F3FE52 | On |
| Thumb | Background | `--acme-switch-thumb-disabled` | `--color-gray-300` | #C8C8C0 | Disabled |
| Thumb | Shadow | — | `--shadow-xs` | 1px 1px 0 0 #0A0A0A | Default |
| Label | Color | `--acme-label-color` | `--fg-primary` | #0A0A0A | Default |
| Label | Color | `--acme-label-disabled-color` | `--fg-tertiary` | #9A9A90 | Disabled |
| Supporting text | Color | — | `--fg-secondary` | #3A3A35 | Default |
| Validation message | Color | `--acme-error-text-color` | `--color-error` | #FF4444 | Error |
| Focus ring | Outline color | `--acme-control-focus-ring` | `--color-focus` | #F3FE52 | Focus-visible |
| Focus ring | Outline width | — | 2px | 2px | Focus-visible |
| Focus ring | Outline offset | — | 2px | 2px | Focus-visible |
---
## States
| State | Visual change | Accessibility |
|-------|--------------|---------------|
| Off | Gray track (#E8E8E0), white thumb at left position | `role="switch"`, `aria-checked="false"` |
| On | Black track (#0A0A0A), yellow thumb (#F3FE52) at right position | `aria-checked="true"` |
| Hover (off) | Track lightens slightly (opacity 0.85) ⚠️ *unresolved — see §Specification completeness* | No change |
| Hover (on) | Track darkens slightly (opacity 0.85) ⚠️ *unresolved* | No change |
| Focus-visible | 2px yellow (#F3FE52) outline on the track, 2px offset | Always visible; never suppressed |
| Disabled | Gray track (#F5F5F0), gray thumb (#C8C8C0), gray label (#9A9A90). No hover or active states. | `aria-disabled="true"`; remove from tab order with `tabindex="-1"` if fully inactive |
| Error | Red border on track, red validation message below | `aria-invalid="true"`, `aria-describedby` → validation message id ⚠️ *error border unresolved* |
The thumb slides between positions with a 150ms ease-out transition. The track fill changes at the same duration. What stays consistent across all states: track and thumb dimensions, label position, and minimum touch target.
---
## Behavior
- Clicking or tapping anywhere on the label or track toggles the switch.
- Space bar or Enter toggles a focused switch.
- State change is immediate. Do not use switches for actions that require a loading state, a confirmation dialog, or any step before the change commits.
- A switch should never open a new page, trigger a complex multi-step flow, or perform a destructive action.
---
## Accessibility
- Use a native `` with `role="switch"` and `aria-checked`. This is currently the best-supported pattern across screen readers.
- Alternatively, use a `