Switch

An on/off control whose change takes effect immediately, with a visible label and an optional description.

Basic usage

Install the package:

pnpm add @design-schema/react @design-schema/tokens

Import the stylesheet once for the app, then the component:

import '@design-schema/tokens/calm-precise/css';
import '@design-schema/react/index.css';
import { Switch } from '@design-schema/react';

Examples

import { Switch } from '@design-schema/react';

<Switch label="Email notifications" />

Props

7 rows
PropTypeDefaultDescriptionPlatforms
label requiredstring—

Visible label naming the thing being turned on or off. Also the accessible name.

Accessibility: Associated with the control (label/for on web, accessibilityLabel on native).

web, lit, rn, swiftui
namestring—

Optional field name. When inside a Form the state is collected as a boolean on every platform; most switches are not in forms. Without `name` a Switch inside a Form does not register (it contributes no key) and its id comes from the platform id generator (useId on web), not the Form's idBase. A Switch never validates and never appears in an error summary.

web, lit, rn, swiftui
checkedboolean—

Controlled state (web, RN, SwiftUI): a controlled switch shows a new state only once this prop changes. Omit for an uncontrolled control. Lit has no controlled mode: the `checked` property is the live state like a native input (it starts from the `checked` attribute, else `defaultChecked`, and every toggle updates it).

web, lit, rn, swiftui
defaultCheckedbooleanfalse

Initial state for an uncontrolled control.

web, lit, rn, swiftui
disabledbooleanfalse

Cannot be toggled. Stays visible, readable and focusable on web, Lit and SwiftUI; a disabled native React Native Switch is not focusable (platform limit, see platforms.rn.notes). A disabled switch contributes no key to the Form's values on any platform (the Form's disabled-field rule, as for Checkbox): on web and RN it stays registered and reports itself through the registration's `isDisabled()`, which the Form already uses to skip a field — one mechanism for the rule, the same one Checkbox uses, rather than unregistering and re-registering as the prop flips; on Lit it calls setFormValue(null) while disabled or form-disabled, so neither a native <form> nor ds-form collects it.

web, lit, rn, swiftui
descriptionstring—

Persistent helper text below the label explaining the effect.

Accessibility: Linked with aria-describedby / accessibilityHint.

web, lit, rn, swiftui
labelPositionstart | endstart

Where the label sits relative to the track. `start` (label, then switch at the row end) is the settings-list convention; `end` matches Checkbox.

web, lit, rn, swiftui

Events

1 row
EventTypeDescriptionPlatforms
onChangeonChange / change / onValueChange

Fired when the user changes the state, with the new boolean. The change is already in effect; there is nothing to submit. A controlled prop change fires nothing, a press that asks for the current value (next equals checked) fires nothing, and nothing fires on mount. The next-equals-checked rule needs a guard only where a platform can report the current value (RN valueChange, SwiftUI); on web and Lit every native toggle flips the value, so no guard is written there.

web, lit, rn, swiftui

Accessibility

Decisions this component leaves to you

Supply an accessible name. This component has no text of its own to fall back on, so without one a screen reader announces its role and nothing else.

Supply the label. It is associated with the control for you, but the words are yours — a placeholder is not a label.

Required props that carry accessibility meaning: label. Each is described in the props table above.

Implicit ARIA role: switch.

Anatomy

trackthumblabeldescription

What generation guarantees

Accessible name — Every instance carries a programmatic name. Where the component has no text of its own to use, the name is a prop the consumer supplies.

Label association — The label is programmatically associated with the control, not merely placed beside it.

Visible focus — Keyboard focus is always drawn, from the theme’s focus tokens, and is never removed.

Keyboard operable — Everything the pointer can do here, the keyboard can do — see the keyboard section.

Target size — Interactive targets are at least 24×24 CSS pixels (WCAG 2.2 Target Size, minimum).

Contrast — Text and meaningful non-text contrast meets WCAG AA; the pairs are listed below.

Reduced motion — Under `prefers-reduced-motion: reduce` the transitions are dropped rather than shortened, and the component still reaches every state.

Contrast pairs

6 rows
ForegroundBackgroundLevel
color.control.selectedForegroundcolor.control.selectedBackgroundAA (non-text) (checked)
color.control.selectedForegroundcolor.control.trackOffAA (non-text)
color.control.selectedBackgroundcolor.backgroundAA (non-text)
color.control.trackOffcolor.backgroundAA (non-text)
color.foregroundcolor.backgroundAA
color.foreground.mutedcolor.backgroundAA

Under `prefers-reduced-motion: reduce` the transitions are dropped rather than shortened, and the component still reaches every state.