# 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 `