Skip to content

Selection

Switch Card

Stable

A selectable card for an immediate on/off setting, available on web (React) and native (React Native). Three sizes, a required label and body, an optional descriptor, and the whole card as the hit target.

Sizes

Small

16px card padding, 44 × 24px switch track.

<SwitchCard size="sm" label="Small" body="Body copy" />

Medium

The default size. 16px padding, 60 × 32px track.

<SwitchCard size="md" label="Medium" body="Body copy" />

States

Rest

A 2px border in the selector tokens, thicker than Card's 1px default. Ahead of Figma, which paints no rest stroke.

<SwitchCard label="Rest" body="Body copy" />

Selected

The same 2px border in the selected colours. Hovering the card moves the fill, border and text to their hover tokens.

<SwitchCard label="Selected" body="Body copy" defaultChecked />

Disabled

Disabled, off

The card takes the disabled colours and every path to the toggle is blocked.

<SwitchCard label="Email notifications" body="Body copy" disabled />

Disabled, on

<SwitchCard label="Email notifications" body="Body copy" disabled defaultChecked />

With descriptor

Label and body

label and body are both required. The body is exposed as the accessible description.

<SwitchCard label="Email notifications" body="We will email you when a job is booked." />

Label, descriptor and body

The optional descriptor sits between the label and the body, and joins the label in the accessible name.

<SwitchCard label="Email notifications" descriptor="Optional second line" body="We will email you when a job is booked." />

Hit target

The whole card toggles

The card is a <label>, so clicking anywhere — label, descriptor or body — toggles the switch exactly once.

<SwitchCard label="Email notifications" body="Click anywhere on this card." />

When to use

Use a switch card when a single on/off setting needs more room to explain itself than a one-line switch label allows — a title, an optional second line, and a body line of context, all inside one clearly-bounded target.

A switch implies the change has already taken effect, so use it for settings that apply immediately rather than values that are submitted with a form.

When not to use

  • A value that is submitted with a form: use a checkbox card. On web that one is a real form control; the switch card is not.
  • A short, self-evident label: use a plain switch. A card adds padding for no benefit when there is nothing to explain.
  • Rich card content — links, buttons, images, anything interactive: not supported. The card is a single label/pressable, so nested interactive content would be invalid. The three text slots are strings for exactly this reason.
  • A group of related settings: compose several cards; a card-list component is not built yet.

Switch Card vs Checkbox Card

The two are the same card with a different control in the corner — same width, same two sizes, same text slots. Pick by what the choice means: a switch card is a setting that takes effect immediately, a checkbox card is a value the user is confirming.

Props

Prop Type Default Description
checked boolean - Controlled state. Omit and use defaultChecked for uncontrolled usage.
defaultChecked boolean false Initial state for uncontrolled usage. Ignored if checked is provided.
onChange (checked: boolean) => void - Fires with the new value on toggle.
size 'sm' | 'md' 'md' Changes the card padding and the control size. Typography never changes with size.
label string - Primary text line. Required.
descriptor string - Optional secondary line beneath the label.
body string - Body copy on its own full-width row beneath the label and control. Required. Exposed as the accessible description on web, and folded into the accessible name on native.
disabled boolean false Blocks every path to the toggle and shows the disabled colours.
id string - Web only. Generated via useId() if omitted; derives the -label, -descriptor and -body ids used for the ARIA wiring.
aria-describedby string - Web only. Appended after the internal body id, so it adds to the accessible description rather than replacing it.
accessibilityLabel string - Native only. Overrides the accessible name outright — the descriptor and body are not appended.
testID string - Native only. Sub-ids: -header (header row), -text (label and descriptor), -body, -switch (control).

Not available on the card, because it owns them itself: labelPosition and showStateIcon (the card owns the layout and never renders the in-track state icon), and aria-label / aria-labelledby on web (the name is derived from label and descriptor).

The card’s props are derived from SwitchProps: it is Omit<SwitchProps, 'label' | 'labelPosition' | 'showStateIcon' | 'aria-label'> plus the required label and body slots and its own descriptor (Switch has no descriptor slot). Any prop Switch accepts — size, id and the rest — is accepted here too, and a prop added to Switch reaches SwitchCard without a second edit.

Import

Web

import { SwitchCard } from '@checkatrade/components-web';

Native

import { SwitchCard } from '@checkatrade/components-native';

Basic usage

Controlled (both platforms):

const [on, setOn] = useState(false);
<SwitchCard
  label="Email notifications"
  body="We will email you when a job is booked."
  checked={on}
  onChange={setOn}
/>;

Uncontrolled (both platforms):

<SwitchCard label="Email notifications" body="Body copy" defaultChecked />

With the optional descriptor:

<SwitchCard
  label="Email notifications"
  descriptor="Optional second line"
  body="We will email you when a job is booked."
/>

Platform status

Platform / AreaStatus
Design (Figma) Caution
Web (React) Stable
Native (React Native) Stable
iOS (Swift) Planned
Android (Kotlin) Planned
Accessibility audit Planned

The Figma source is marked Caution because the selected border resolves to a different value than Figma draws, the rest stroke is ahead of Figma, and the switch track sizes in the card spec do not match the shipped switch — see Token gaps below.

Accessibility

Web

  • The card is a <label> wrapping the text block and the control, so the whole card is a click target — clicking the label, descriptor or body toggles the switch exactly once. A <label> can label a <button>, which is what makes this work for the switch’s control.
  • The control keeps its own semantics: a <button role="switch">, focusable and toggled with Space/Enter. The card does not add a second announcement.
  • The accessible name is the label plus the descriptor, joined as "label. descriptor". The body is exposed as the accessible description, not concatenated into the name.
  • The focused state is a 4px focus-ring outline drawn outside the card, and appears on keyboard focus rather than mouse click.

React Native

  • The card’s root Pressable is not an accessibility node and is not a tab stop, so it adds no second announcement.
  • The control keeps its own semantics: accessibilityRole="switch" with accessibilityState={{ checked, disabled }}.
  • Native has no accessible-description slot, so the name is the label, descriptor and body joined with a full stop — e.g. "Email notifications. Optional second line. Body copy". A consumer accessibilityLabel overrides it outright.
  • The text block and the body are visible but hidden from assistive tech on both native and the web surface, so the control stays the single source of the accessible name.
  • The whole card is the tap target. Pressing the text or the body toggles the control exactly once.

Target size

The whole card is the target on both platforms, so the effective hit area is far larger than the switch track at every size.

Token gaps

  • Native Text cannot take the card’s label tokens. Web colours the label, descriptor and body from the selector label tokens in every state. Native Text has no colour escape hatch, so native keeps the default ink and dims a disabled card’s text instead.
  • The switch track sizes in the card spec do not match the shipped switch. The spec’s extraction measured the control at 40 × 24 / 56 × 32 / 70 × 40; the shipped switch is 44 × 24 / 60 × 32 / 80 × 40, and the card renders the real switch. The shipped values are what the component uses. Raised with Design.
  • No surface-tone-aware card tokens. The card’s selector tokens have no inverse counterpart, so inside an inverse surface the card keeps its normal-surface colours. This applies to the control’s tokens too.
  • The selected border is a close match, not an exact one. Figma draws it at #0036fa; the card binds --color-selector-border-selected, which resolves to #040154. The token is what the card uses — there is no literal — and the difference is a deliberate deviation signed off with Design.
  • The native card and the web card are close but not identical. Fill, border, hover and the selected and disabled states match. Native cannot apply the label tokens, so its text does not recolour on hover or selection, and a disabled card dims its text instead.

No releases yet.

Select a brand to demo:
Active brand: checkatrade