Sizes
Small
16px card padding, 16px checkbox box.
<CheckboxCard size="sm" label="Small" body="Body copy" />Medium
The default size. 16px padding, 24px box.
<CheckboxCard 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.
<CheckboxCard 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.
<CheckboxCard label="Selected" body="Body copy" defaultChecked />Disabled
Disabled, unselected
The card takes the disabled colours and every path to the toggle is blocked.
<CheckboxCard label="Accept terms" body="Body copy" disabled />Disabled, selected
<CheckboxCard label="Accept terms" body="Body copy" disabled defaultChecked />With descriptor
Label and body
label and body are both required. The body is exposed as the accessible description.
<CheckboxCard label="Accept terms" body="You can withdraw your consent at any time." />Label, descriptor and body
The optional descriptor sits between the label and the body, and joins the label in the accessible name.
<CheckboxCard label="Accept terms" descriptor="Optional second line" body="You can withdraw your consent at any time." />Hit target
The whole card toggles
The card is a <label>, so clicking anywhere — label, descriptor or body — toggles the checkbox exactly once.
<CheckboxCard label="Accept terms" body="Click anywhere on this card." />When to use
Use a checkbox card when a single boolean choice needs more room to explain itself than a one-line checkbox label allows — a title, an optional second line, and a body line of context, all inside one clearly-bounded target.
Because the whole card is the hit target, it also suits layouts where the target needs to be comfortably large (pricing rows, plan choices, permission toggles) without losing the checkbox’s meaning.
When not to use
- A setting that applies immediately, with no save/submit step: use a switch card instead — its on/off framing implies the change has already taken effect.
- A short, self-evident label: use a plain checkbox. 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 options: compose several cards; a card-list component is not built yet.
Checkbox Card vs Switch 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 checkbox card is a value the user is confirming, a switch card is a setting that takes effect immediately.
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-labelledby | string | - | Web only. When supplied it takes precedence over the derived name: appended after the internal label/descriptor ids, so it adds to the accessible name rather than replacing it. |
| 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, -checkbox (control). |
The card’s props are derived from CheckboxProps: it is Omit<CheckboxProps, 'label' | 'labelPosition' | 'aria-label'> plus the required label and body slots, so any prop Checkbox accepts — size, descriptor, id, name, value, required and the rest — is accepted here too, and a prop added to Checkbox reaches CheckboxCard without a second edit. labelPosition is omitted because the card owns the layout; aria-label (web) is omitted because the card composes the accessible name from label and descriptor.
Import
Web
import { CheckboxCard } from '@checkatrade/components-web';Native
import { CheckboxCard } from '@checkatrade/components-native';Basic usage
Controlled (both platforms):
const [accepted, setAccepted] = useState(false);
<CheckboxCard
label="Accept terms"
body="You can withdraw your consent at any time."
checked={accepted}
onChange={setAccepted}
/>;Uncontrolled (both platforms):
<CheckboxCard label="Accept terms" body="Body copy" defaultChecked />With the optional descriptor:
<CheckboxCard
label="Accept terms"
descriptor="Optional second line"
body="You can withdraw your consent at any time."
/> 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, and the rest stroke is ahead of Figma — 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 checkbox exactly once. - The control is a real
<input type="checkbox">, focusable and toggled with Space. The card does not add a second announcement. - The accessible name is the label and the descriptor joined as
"label. descriptor"— the same stringSwitchCardproduces for the same content. The body is exposed as the accessible description, not concatenated into the name — web has a description slot, so it uses it. - The styled box is decorative (
aria-hidden="true"); state lives on the input. - 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="checkbox"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.
"Accept terms. 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 16–32px control 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. - 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.