Default
Show the codeHide the code
Default, code
<Switch>Two-factor authentication</Switch>An on/off setting that applies immediately. The thumb carries a tick or a cross.
These are the real React components. Use them with a mouse, touch or keyboard, and switch the theme and density for every example at once.
Default, code
<Switch>Two-factor authentication</Switch>On, code
<Switch defaultSelected>Two-factor authentication</Switch>With description, code
<Switch
description="A summary every Monday morning."
labelPosition="start"
className="max-w-md"
>
Weekly digest
</Switch>Disabled, code
<Switch isDisabled defaultSelected>Two-factor authentication</Switch>Mobile size, code
<Switch size="lg" defaultSelected>Two-factor authentication</Switch>Loading, code
<Switch isLoading defaultSelected>Sync contacts</Switch>Label hidden, code
<Switch hideLabel defaultSelected>Weekly summary</Switch>Invalid, code
<Switch
isInvalid
errorMessage="Could not turn on sync. Check your connection and try again."
className="max-w-md"
>
Sync contacts
</Switch>The 31 design tokens Switch reads, with the value each has in the theme and density chosen above. Change a token and the component follows, in Figma and in code.
| Token | Value now | CSS variable |
|---|---|---|
| color.bg.muted | … | --sd-color-bg-muted |
| color.border.subtle | … | --sd-color-border-subtle |
| color.text.default | … | --sd-color-text-default |
| color.text.disabled | … | --sd-color-text-disabled |
| field.error | … | --sd-field-error |
| field.error-icon | … | --sd-field-error-icon |
| field.hint | … | --sd-field-hint |
| input.border-invalid | … | --sd-input-border-invalid |
| motion.duration.fast | … | --sd-motion-duration-fast |
| motion.easing.standard | … | --sd-motion-easing-standard |
| radius.full | … | --sd-radius-full |
| size.control.md | … | --sd-size-control-md |
| size.icon.lg | … | --sd-size-icon-lg |
| size.icon.sm | … | --sd-size-icon-sm |
| spacing.gap.md | … | --sd-spacing-gap-md |
| spacing.gap.xs | … | --sd-spacing-gap-xs |
| switch.border | … | --sd-switch-border |
| switch.border-width | … | --sd-switch-border-width |
| switch.glyph-off | … | --sd-switch-glyph-off |
| switch.glyph-on | … | --sd-switch-glyph-on |
| switch.radius | … | --sd-switch-radius |
| switch.thumb-off | … | --sd-switch-thumb-off |
| switch.thumb-on | … | --sd-switch-thumb-on |
| switch.thumb-size | … | --sd-switch-thumb-size |
| switch.track-height | … | --sd-switch-track-height |
| switch.track-off | … | --sd-switch-track-off |
| switch.track-off-pressed | … | --sd-switch-track-off-pressed |
| switch.track-on | … | --sd-switch-track-on |
| switch.track-on-pressed | … | --sd-switch-track-on-pressed |
| switch.track-width | … | --sd-switch-track-width |
| target.min | … | --sd-target-min |
The npm package isn’t published yet. When it is, this is the install line; until then, this page runs the same build of the package (from commit fa3abf8).
Install
npm i @superduperui/react @superduperui/tokensImport
import { Switch } from "@superduperui/react"Switch also takes the props of the React Aria component it’s built on.
| Prop | Type | What it does |
|---|---|---|
childrenrequired | ReactNode | Visible label (required). |
description | string | Optional second line. |
labelPosition | 'start' | 'end'Default 'end' | Put the label before the track (settings rows) or after it (default). |
size | 'default' | 'lg'Default 'default' | default; lg (mobile): a 25% larger track and thumb. Both sit in a 44px row (2.5.5). |
isLoading | booleanDefault false | Saving the change: the thumb shows a spinner, aria-busy is set and presses are ignored until it ends. |
isInvalid | booleanDefault false | The setting could not be applied (for example a failed save): shows errorMessage with an icon. |
errorMessage | string | Why the setting is invalid; linked to the switch by aria-describedby. |
hideLabel | booleanDefault false | Keep the label (and description) as the accessible name but hide them visually, so the track hugs: for setting rows and grids where a visible title already names the switch. The hit area stays 44px; an error stays visible. |
Screen readers announce "Two-factor authentication, switch, off".
WCAG 2.2 AAA criteria the component meets in Comfortable density: 1.4.6, 1.4.9, 2.1.3, 2.3.2, 2.4.13, 2.5.5, 2.5.6, 2.3.3.
Compact density meets AA (2.5.8, 24px targets), not AAA.
| axe-core (WCAG A, AA, AAA and best practice) | 180 runs across every story, theme, density and width: 0 violations |
|---|---|
| Rendered text contrast | 115 text runs: 0 below 7:1, lowest 8.43:1 |
| Target size | 34 targets: 0 under 44px in Comfortable |
| Focus appearance (2.4.13) | 36 focus stops in 3 browser engines: all pass |
| Keyboard script | Chromium 3 of 3, Firefox 3 of 3, WebKit 3 of 3 |
Manual passes with VoiceOver and NVDA haven’t been done yet.
Each step is scripted and run in Chromium, Firefox and WebKit.
| Key | What happens |
|---|---|
| Tab | Tab reaches the switch (off) |
| Space | Space turns it on |
| Space | Space turns it off |
The Figma component is Switch, with Checked (False, True); State (Default, Hover, Pressed, Focus, Disabled); Label position (End, Start); Size (Default, Mobile); Label visibility (Visible, Hidden); Width (Fit, Full). The Figma library isn’t published yet.
Figma to code: Checked (False, True = defaultSelected); State (Default, Hover, Pressed, Focus, Disabled = isDisabled); Label position → labelPosition (End = "end", Start = "start"); Label → children; Description → description when Show description; Size → size (Default = "default", Mobile = "lg"); Invalid → isInvalid; Error → errorMessage when Invalid; Loading → isLoading; Label visibility → hideLabel (Visible = false, Hidden = true); Width (Fit, Full; preview only).