Design 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.

Choosing between the selection and toggle controls
ControlThe decisionAppliesUse when
CheckboxMultiple independent selectionsOn submitServices, permissions, filters
Indeterminate checkboxPartial or hierarchical selectionOn submitSelect all, parent of a nested set
RadioExactly one option from a groupOn submitBilling frequency, contact method
SwitchAn independent on/off settingImmediatelyNotification preferences, feature access
Selection cardA rich option needing contextOn submitPlans, 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.

States shared by checkbox, radio and switch
StateCheckboxRadioSwitch
DefaultStrong border, surface fillStrong border, surface fillNeutral track, thumb left
HoverPrimary borderPrimary borderStronger track
FocusShared focus ringShared focus ringShared focus ring
SelectedPrimary fill + check glyphPrimary border + dotPrimary track + check glyph
IndeterminatePrimary fill + minus glyphn/an/a
ErrorError border + group messageError border + group messagen/a — settings do not validate
DisabledMuted surface, readable labelMuted surface, readable labelReduced opacity, readable label
Loadingn/an/aSpinner in the thumb, interaction blocked
Keyboard behaviour
ControlTabArrow keysSpace
CheckboxFocuses each checkbox—Toggles
Checkbox groupFocuses each option in order—Toggles the focused option
Radio groupEnters the group at the selected optionMoves and selectsSelects the focused option
SwitchFocuses the switch—Toggles (also Enter)
Selection cardSame as the control it rendersMoves within a single-choice groupSelects

Specification

Sizes and tokens shared by the selection family.
PropertySmallMediumLargeToken source
Control box16px20px24px--ds-space scale
Switch track16 × 28px20 × 36px24 × 44px--ds-space scale
Label typebody-smbody-smbody-md--ds-text-body-*
Description typecaptioncaptionbody-sm--ds-text-caption / body-sm
Row gap8px8px8px--ds-space-2
Radiusradius-xs / fullradius-xs / fullradius-xs / full--ds-radius-*
Selectedbg-primary + glyphbg-primary + glyphbg-primary + glyph--color-primary
Errorborder-error + messageborder-error + messageborder-error + message--color-error
Disabledmuted surface + 60% opacitymuted surface + 60% opacitymuted surface + 60% opacity--color-background-muted
Pointer targetds-targetds-targetintrinsic--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

Label, description and helper text

Helps us prioritise the features your team uses most. No personal data leaves your workspace.

Applies to cases closed for more than 90 days.

Description rows in every state

Save my login details for next time.
Save my login details for next time.
Save my login details for next time.
Save my login details for next time.

Sizes

Dense tables and filter panels.
Default for forms.
Touch-first and onboarding flows.

Accessibility

  • The label is wired to the box with htmlFor / id; clicking the label toggles it.
  • description and errorText are linked with aria-describedby and read after the label.
  • errorText renders an icon plus text, so the error is never colour-only, and sets aria-invalid.
  • The painted box is smaller than 24px, so ds-target expands the pointer target to 24px (44px on coarse pointers).

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

Record visibility(required)

Controls who can open this record from search.

Only you and the record owner.
Everyone in your department.
Anyone with a Centrim account.
Requires an admin approval.

Error state

Preferred contact time(required)

Disabled and disabled-selected

Escalation route
Selected by your administrator.
Not available on your plan.

Sizes and horizontal layout

Billing period
Reminder frequency
Save my login details for next time.
Save my login details for next time.

Accessibility

  • RadioGroup is a Radix roving-focus group: Tab enters the group, arrow keys move the selection.
  • Each option's description is linked with aria-describedby.
  • Group-level errorText sets aria-invalid on the group and is announced with role=alert.
  • Options use ds-target so the pointer target clears the WCAG 2.5.8 minimum.

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

A Monday morning digest of open cases assigned to you.
Resolved records stay visible with a muted status tag.

Sizes and label position

Sizes with a description

Save my login details for next time.
Save my login details for next time.
Save my login details for next time.

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.

Applies immediately for your account.
Saving…

Accessibility

  • Radix renders role=switch with aria-checked, so the on/off state is announced.
  • The thumb carries a check / cross glyph, so state is legible without colour.
  • label is wired with htmlFor; description is linked with aria-describedby.
  • ds-target expands the pointer target without changing the painted track.

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

Notification channels

Choose how we reach you about cases assigned to your team.

Daily digest to your work address.
Only for urgent incidents.
Real time on the Centrim app.

Permissions with a disabled option

Case manager permissions(required)

Applies to every member of the Case Managers role.

Granted by the role.

Deletion is permanent and audited.

Horizontal filter selection

Status

Filters apply as soon as you select them.

Accessibility

  • The set is a role=group labelled by the group label, so screen readers announce it as one unit.
  • The group description, helper text and error message are all linked with aria-describedby.
  • The select-all row is a real tri-state checkbox: indeterminate communicates a partial selection.
  • Error messaging sits once at group level rather than repeating on every option.

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

Preferred communication method(required)

We use this for case updates and appointment reminders.

Sent to your registered address.
During office hours only.
Short updates, no attachments.
Currently unavailable for this workspace.

Accessibility

  • One group landmark labelled by the group label; arrow keys move between options.
  • Only one option in the group can be selected, enforced by Radix.
  • Group description and error message are linked with aria-describedby.
  • Disabled options stay in the reading order and are announced as unavailable.

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)

Workspace plan(required)

Change or cancel at any time. Prices exclude VAT.

Stacked tiles with a footer block

How do you want to start?
Plan detail

Feature selection (multiple)

Modules to enable

You can turn modules on or off later in workspace settings.

Person cards

Add reviewers

Selected and disabled

Assigned plan

Error and sizes

Support level(required)

Accessibility

  • Each card is a real Radix radio or checkbox — the whole surface is the control.
  • aria-labelledby points at the card title, aria-describedby at its description.
  • Focus is drawn on the card with the shared focus ring via has-focus-visible.
  • Selected state shows a filled indicator plus a border and surface change, never colour alone.
  • Group errorText sets aria-invalid on the cards and is announced once.

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

ResidentRoomService
Margaret EllisWillow 12Residential care
Ade OkaforWillow 14Residential care
Priya RamanOak 03Respite

Accessibility

  • The header checkbox has an accessible name (“Select all residents”) and reflects partial selection as indeterminate.
  • Each row checkbox names its row, so a screen reader announces which record is selected.
  • Selection count is announced through a polite status region above the table.

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

Assigned services

Applies to every visit for this resident.

Services(required)

Choose everything the care team should deliver.

Weekly housekeeping visits.
Repairs and safety checks.
Overnight on-site presence.
Billing frequency(required)

How the family is invoiced for these services.

Invoiced on the 1st.
Two weeks' fees discounted.
Agreed with the finance team.

Accessibility

  • The group is a single role=group with one accessible name, so options are announced as a set.
  • Required status uses the shared FormField convention: an asterisk plus a hidden “(required)”.
  • The error message is linked to the group with aria-describedby and pairs an icon with the text.

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.