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 / Area | Status |
|---|---|
| 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
focusedstate 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
Pressableis not an accessibility node and is not a tab stop, so it adds no second announcement. - The control keeps its own semantics:
accessibilityRole="switch"withaccessibilityState={{ 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 consumeraccessibilityLabeloverrides 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
Textcannot take the card’s label tokens. Web colours the label, descriptor and body from theselectorlabel tokens in every state. NativeTexthas 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
selectortokens 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
selectedborder 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.