Centrim Design System — a token-first, accessible React component system.
Selection & toggle controls
Checkbox, indeterminate select-all, radio, switch, their group patterns, selection cards and table selection — one V2 family sharing the same typography, spacing, radius, focus and disabled language as Input, Select and Button.
Overview
What is this?
Every way a user commits to a choice in Centrim: independent opt-ins (Checkbox), exclusive choices (Radio), immediate settings (Switch), their labelled group wrappers, and card-sized choices that need context (SelectionCard).
When to use it
- Checkbox for independent options inside a submitted form.
- Radio for one choice from a small, comparable set.
- Switch for a setting that applies the moment it is flipped.
- SelectionCard when an icon, description or price is essential.
How to use it
- Always wrap a set in CheckboxGroup or RadioField so the label and error live once.
- Keep one size per form: small in dense filters, medium in forms, large for touch.
- Attach validation with errorText at group level, not per option.
What to avoid
- A bare styled div with a click handler instead of a real control.
- Selection cards where a plain checkbox would do.
- Communicating selected, error or disabled state with colour only.
Which control?
Pick by the decision the user is making, not by how much space is available.
| Control | The decision | Applies | Use when |
|---|---|---|---|
| Checkbox | Multiple independent selections | On submit | Services, permissions, filters |
| Indeterminate checkbox | Partial or hierarchical selection | On submit | Select all, parent of a nested set |
| Radio | Exactly one option from a group | On submit | Billing frequency, contact method |
| Switch | An independent on/off setting | Immediately | Notification preferences, feature access |
| Selection card | A rich option needing context | On submit | Plans, services, notification methods |
Switch vs Checkbox vs Radio
A Checkbox is an answer inside a form: nothing happens until the form is submitted. A Switch is the action itself — flipping it persists straight away, which is why it carries a loading state and never appears above a Save button. A Radio group is a single mutually exclusive answer, so several Switches must never stand in for one Radio group.
The three never trade appearances: a square box means “one of many”, a circle means “one only”, and a track with a thumb means “this is live”.
States & keyboard behaviour
The same state language across the family, and the native interaction users expect.
| State | Checkbox | Radio | Switch |
|---|---|---|---|
| Default | Strong border, surface fill | Strong border, surface fill | Neutral track, thumb left |
| Hover | Primary border | Primary border | Stronger track |
| Focus | Shared focus ring | Shared focus ring | Shared focus ring |
| Selected | Primary fill + check glyph | Primary border + dot | Primary track + check glyph |
| Indeterminate | Primary fill + minus glyph | n/a | n/a |
| Error | Error border + group message | Error border + group message | n/a — settings do not validate |
| Disabled | Muted surface, readable label | Muted surface, readable label | Reduced opacity, readable label |
| Loading | n/a | n/a | Spinner in the thumb, interaction blocked |
| Control | Tab | Arrow keys | Space |
|---|---|---|---|
| Checkbox | Focuses each checkbox | — | Toggles |
| Checkbox group | Focuses each option in order | — | Toggles the focused option |
| Radio group | Enters the group at the selected option | Moves and selects | Selects the focused option |
| Switch | Focuses the switch | — | Toggles (also Enter) |
| Selection card | Same as the control it renders | Moves within a single-choice group | Selects |
Specification
| Property | Small | Medium | Large | Token source |
|---|---|---|---|---|
| Control box | 16px | 20px | 24px | --ds-space scale |
| Switch track | 16 × 28px | 20 × 36px | 24 × 44px | --ds-space scale |
| Label type | body-sm | body-sm | body-md | --ds-text-body-* |
| Description type | caption | caption | body-sm | --ds-text-caption / body-sm |
| Row gap | 8px | 8px | 8px | --ds-space-2 |
| Radius | radius-xs / full | radius-xs / full | radius-xs / full | --ds-radius-* |
| Selected | bg-primary + glyph | bg-primary + glyph | bg-primary + glyph | --color-primary |
| Error | border-error + message | border-error + message | border-error + message | --color-error |
| Disabled | muted surface + 60% opacity | muted surface + 60% opacity | muted surface + 60% opacity | --color-background-muted |
| Pointer target | ds-target | ds-target | intrinsic | --ds-target-min |
Why one shared size map
Checkbox, Radio and Switch read their geometry and typography from a singleselectionSizesmap, so a small checkbox and a small switch line up on the same row of a filter panel and no control invents its own type step.
Components
Checkbox
A single independent yes/no choice inside a form that is submitted, and the tri-state parent of a partially selected set.
import { Checkbox } from "@/design-system";
When to use
- Opting in or out of one thing (terms, a single notification channel).
- Selecting several items from a list of independent options.
- Representing a partially selected set with the indeterminate state.
When not to use
- For a setting that applies immediately — use Switch.
- For a choice between mutually exclusive options — use Radio.
- When each option needs an icon and a paragraph of context — use SelectionCard.
States
You must accept the agreement before continuing.
Label, description and helper text
Applies to cases closed for more than 90 days.
Description rows in every state
Sizes
Radio
One choice from a small set of mutually exclusive options, always rendered inside a labelled group.
import { Radio, RadioField, RadioGroup } from "@/design-system";
When to use
- Two to five mutually exclusive options that benefit from being visible at once.
- Choices where comparing the options matters (billing period, visibility level).
When not to use
- More than about six options — use Select.
- Independent options that can be combined — use CheckboxGroup.
Radio + label, description and states
Controls who can open this record from search.
Error state
Select a contact window so we know when to call.
Disabled and disabled-selected
Sizes and horizontal layout
Switch
An immediate on/off setting: flipping it takes effect straight away, with no save step.
import { Switch } from "@/design-system";
When to use
- Settings screens where each change persists on toggle.
- Enabling or disabling a feature for the current user or workspace.
When not to use
- Inside a form that has a Save action — use Checkbox.
- For anything destructive — pair the change with a ConfirmDialog instead.
Off, on, disabled
Switch + description
Sizes and label position
Sizes with a description
Loading — the setting is being saved
The toggle keeps its current position, blocks interaction and shows a spinner in the thumb, so it never claims a change that has not persisted yet.
CheckboxGroup
A labelled set of related checkboxes with one shared description, one shared error message and an optional select-all row.
import { CheckboxGroup, Checkbox } from "@/design-system";
When to use
- Multi-select preferences: notification channels, permissions, filters.
- Any set where a select-all / partial state is useful.
When not to use
- A single standalone opt-in — use Checkbox on its own.
- Mutually exclusive options — use RadioField.
Notification preferences with select all
Choose how we reach you about cases assigned to your team.
Permissions with a disabled option
Applies to every member of the Case Managers role.
Granted by the role.
Horizontal filter selection
Filters apply as soon as you select them.
RadioField
A labelled radio group: group label, description, single selection and one shared error message.
import { RadioField, Radio } from "@/design-system";
When to use
- A required single choice inside a form (plan, escalation level, visibility).
- Anywhere the options should be compared side by side.
When not to use
- Long option lists — use Select or Combobox.
- Options that can be combined — use CheckboxGroup.
Communication preference
We use this for case updates and appointment reminders.
SelectionCard
A radio or checkbox rendered as a card, for choices that need an icon, a title, supporting copy and metadata.
import { SelectionCard, SelectionCardGroup } from "@/design-system";
When to use
- Plan and pricing selection, where each option needs price and inclusions.
- Feature or module selection during onboarding.
- Choices where an icon materially helps recognition.
When not to use
- When a label alone is enough — use Checkbox or Radio.
- For long lists — cards cost too much vertical space.
Plan selection (single)
Change or cancel at any time. Prices exclude VAT.
Stacked tiles with a footer block
Feature selection (multiple)
You can turn modules on or off later in workspace settings.
Person cards
Selected and disabled
Error and sizes
Choose a support level to finish setting up billing.
Table selection
Row selection in tables uses the same Checkbox as forms: a tri-state select-all in the header, one checkbox per row, and a bulk action bar once anything is selected. There is no table-specific checkbox.
import { Checkbox, Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/design-system";
When to use
- Lists where an action applies to several records: assign a service, export, archive.
- Any table whose rows can be acted on in bulk.
When not to use
- Tables that only navigate to a record — make the row a link instead.
- Single-choice pickers — use Radio or a Select.
Bulk resident selection
1 of 3 residents selected
| Resident | Room | Service | |
|---|---|---|---|
| Margaret Ellis | Willow 12 | Residential care | |
| Ade Okafor | Willow 14 | Residential care | |
| Priya Raman | Oak 03 | Respite |
Form integration
Checkbox groups and radio groups validate through the same system as every other field: the group owns the label, the required marker and the single error message, and errors appear on submit rather than while the user is still deciding.
import { Form, FormSection, CheckboxGroup, RadioField, FormActions } from "@/design-system";
When to use
- Selection sets inside a form that has a Save or Create action.
- Required groups such as “assigned services” or “billing frequency”.
When not to use
- Immediate-apply settings — use Switch, which persists on toggle.
Required group with submit validation
Do / Don't
Do
- Wrap related controls in CheckboxGroup or RadioField so the label, description and error appear once.
- Use the indeterminate checkbox for a partially selected set, driven by real selection state.
- Use Switch only where the change persists immediately.
- Give every option a label; use description for the detail that would otherwise bloat it.
- Keep selection cards to a handful of options laid out in 2–3 columns.
- Show a group error only after submit, and keep the message on the group, not each option.
- Use the same Checkbox for table selection, with the header box driven by real selection counts.
Don't
- Don't repeat the same error message on every option in a group.
- Don't use a Switch inside a form with a Save button.
- Don't build a bespoke selectable div — SelectionCard already renders a real radio or checkbox.
- Don't shrink the painted control to fit a dense layout; use size="small", which keeps ds-target.
- Don't mix sizes within one group or form section.
- Don't flip a Switch optimistically — show its loading state until the change persists.
- Don't use several Switches for a mutually exclusive choice; that is a Radio group.