Skip to main content

Design system components

Switch

Toggle control with label placement, loading, and helper text.

Status: stable. Group: form. Markdown version: /design-system/components/switch.md

When to use

Switch is an on/off toggle whose effect applies immediately, like enabling dark mode or notifications. It renders a real button with role=switch, supports label placement, a loading state for async flips, and carries its own helper and error text.

Use Switch only when flipping it takes effect immediately; a choice submitted later is a Checkbox.

When not to use

  • Do not use Switch inside a form whose values are submitted in a batch. Use Checkbox there so the choose-then-Submit affordance is honest.
  • Do not rely on colour alone to convey state; the position of the thumb is the primary visual cue, and forced-colors fallbacks preserve that position semantic.
  • Do not use a Switch inside a submitted form for consent; that is Checkbox territory.
  • Do not use negative labels (Disable sounds); keep the label positive so on means yes.

Look-alikes

  • Badge: Do not use Badge as an interactive action or toggle; use Button for actions and Switch or Checkbox for state.
  • Button: Do not use Button for an on/off state; use Switch. For multi-select within a group, use Checkbox or CheckboxGroup.
  • Checkbox: Do not use a Switch inside a submitted form for consent; that is Checkbox territory.

Props

Switch props
PropTypeDefaultDescriptionRequired
disabledbooleanfalseDisables interaction and dims the control.No
loadingbooleanfalseShows a loading spinner and ignores activation; sets aria-busy and aria-disabled. The button is not disabled and stays focusable.No
size"sm" | "md" | "lg"mdControl size.No
onCheckedChange(checked: boolean) => voidnoneCalled with the next checked value when the switch is toggled.No
labelReact.ReactNodenoneVisible label. Without one, pass aria-label or aria-labelledby; a switch with no name logs a development warning.No
labelPlacement"left" | "right" | "top"rightWhere the label sits relative to the control.No
helperTextstringnoneSupporting text rendered beneath the control; always rendered, below error when both are set.No
errorstringnoneError message beneath the control; sets aria-invalid and aria-describedby.No
defaultCheckedboolean | undefinednoneInitial checked state for uncontrolled use. Mutually exclusive with checked.No
checkedbooleannoneChecked state for controlled use. Mutually exclusive with defaultChecked.No

Variants

  • Available sizes: sm, md, lg (default md).
  • Allowed labelPlacement values: left, right, top (default right).

Invalid combinations

  • MUST NOT: checked and defaultChecked set together. checked and defaultChecked never coexist in a valid SwitchProps branch.

Keyboard

Switch keyboard behaviour
KeyBehaviour
SpaceActivates or toggles the focused control. (standard)
EnterActivates the focused control. (standard)

Rows marked (standard) name a key the contract lists without a description; the behaviour shown is the usual one for that key.

Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

Switch rules
LevelRuleHow it is checked
MUST NOTchecked and defaultChecked set together. checked and defaultChecked never coexist in a valid SwitchProps branch.Checked by the MCP tool validate_component_usage.
MUSTARIA: Role=switch.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Aria-checked reflects state.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Named by the visible label through aria-labelledby; without label, by the aria-label or aria-labelledby the consumer passes. With none of these, the switch has no accessible name and logs a development warning.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: While loading, the button stays enabled and focusable and sets aria-busy and aria-disabled; activation does not flip the state (a consumer onClick still runs).Not yet verified: no evidence record. WCAG 4.1.2.
MUSTRequired stories produce no axe violations at the configured severity.Automated by npm run test:stories:matrix:ci. WCAG 2.5.8.
MUSTThe accessibility tree (role, name, state) matches the committed snapshot for every required story.Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTEvery documented keyboard interaction works as specified.Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-11-04.
SHOULDLabel the thing being controlled (Email notifications), never the current state (On).Not stated in the contract.
SHOULDUse loading while an async toggle is in flight; it sets aria-busy and aria-disabled and ignores further flips until it clears.Not stated in the contract.
SHOULDUse helperText for consequences worth knowing before flipping (Applies to all devices).Not stated in the contract.

Install and import

  • Install: npm install @digitaltableteur/react (version 0.9.1, restricted access on npm).
  • Import: import { Switch } from "@digitaltableteur/react";
  • Entry point: @digitaltableteur/react/forms exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.
  • Also available as the custom element <dt-switch>; see the custom elements manifest.