Centrim Design System — a token-first, accessible React component system.
V2 audit — Form elements
Actions and binary/exclusive choices. In the legacy app these were five different button treatments and three checkbox styles; V2 collapses them into one Button variant map and one selection family.
Badge / Tag
Badge labels status, Tag labels a categorical attribute, Chip is an interactive/removable filter value.
BadgeTagChipChipGroupLive component
Legacy mapping
Legacy screens used coloured pills for status, category and filters interchangeably. V2 separates by behaviour: static status (Badge), static attribute (Tag), interactive (Chip).
| Attribute | Specification |
|---|---|
| Default | Badge variant="neutral", size="medium" |
| Variants | neutral · primary · success · warning · error · info · outline |
| Sizes | small · medium |
| States | default · hover / focus (Chip only) · selected (Chip) · removable (Chip) |
| Icon behaviour | Optional leading status dot or Lucide icon at `--size-icon-xs`; remove affordance is a labelled control. |
| Spacing | px-2 / px-2.5, gap-1. |
| Typography | `text-label-sm` / `text-caption`, weight 500. |
| Border radius | `rounded-full` for status and chips, `rounded-sm` for tags. |
| Colour usage | `*-subtle` background with its matching `*-subtle-foreground`; never colour alone — the text names the state. |
| Responsive | Wraps within its container; never truncates a status word. |
When to use
- Record status
- Categorical labels
- Applied filter values
When not to use
- As a button
- For counts inside a table cell — use plain text
Accessibility
- Status meaning is in the text, not the hue.
- Removable chips expose a named remove control, not a bare ×.
Checkbox
Independent on/off choice, with tri-state support for parent rows.
CheckboxLive component
They receive a read-only summary.
You must accept the policy.
Legacy mapping
Legacy checkboxes came in two sizes with an inconsistent indeterminate glyph. V2 fixes two sizes and a single Minus/Check glyph pair.
| Attribute | Specification |
|---|---|
| Default | size="medium" (20px box) |
| Variants | unchecked · checked · indeterminate |
| Sizes | small (16px) · medium (20px) |
| States | default · hover · focus-visible · checked · indeterminate · disabled · error |
| Icon behaviour | Check / Minus at `--size-icon-xs`, stroke 3, `primary-foreground`. |
| Spacing | gap-2 to the label; helper text indents to the label edge. |
| Typography | Label `text-body-sm`; helper/error `text-caption`. |
| Border radius | `rounded-xs`. |
| Colour usage | `bg-primary` + `border-primary` when checked; error borders use `border-error`. |
| Responsive | Label wraps under the box on narrow widths; the box never shrinks. |
When to use
- Optional terms
- Independent options
- Table row selection
When not to use
- One-of-many — use Radio
- Instant-apply settings — use Switch
Accessibility
- Radix Checkbox with a real label association.
- State carried by the glyph, not colour.
- Pointer target ≥24px (≥44px on coarse pointers) via `ds-target`.
Radio
One choice from a small, always-visible set.
RadioRadioGroupRadioFieldLive component
Legacy mapping
Legacy radios were sometimes styled buttons. V2 keeps real radios and routes button-like exclusive choices to SegmentedControl.
| Attribute | Specification |
|---|---|
| Default | size="medium" |
| Variants | with label · with label + description |
| Sizes | small (16px) · medium (20px) |
| States | default · hover · focus-visible · checked · disabled · error |
| Icon behaviour | No icon; a filled dot indicates selection. |
| Spacing | gap-2 to label, gap-2 between options. |
| Typography | Label `text-body-sm`, description `text-caption`. |
| Border radius | `rounded-full`. |
| Colour usage | `border-primary` + `bg-primary` dot. |
| Responsive | Vertical by default; horizontal only for two short options. |
When to use
- 2–5 exclusive options that should all stay visible
When not to use
- More than ~6 options — use Select
- Independent options — use Checkbox
Accessibility
- Radix RadioGroup: arrow keys move selection, one tab stop per group.
- Pointer target ≥24px (≥44px on coarse pointers) via `ds-target`.
Switch
Immediately-applied on/off setting.
SwitchLive component
Applies immediately.
Legacy mapping
Legacy settings mixed switches and checkboxes for the same behaviour. V2 rule: Switch = applies immediately; Checkbox = applies on save.
| Attribute | Specification |
|---|---|
| Default | size="medium" |
| Variants | with label · label + description |
| Sizes | small · medium |
| States | off · on · hover · focus-visible · disabled |
| Icon behaviour | No icon inside the track; pair with a status text if the effect is not obvious. |
| Spacing | gap-3 between control and label block. |
| Typography | Label `text-label-md`, description `text-body-sm`. |
| Border radius | `rounded-full`. |
| Colour usage | `bg-primary` when on, `bg-border-strong` when off. |
| Responsive | Control stays right-aligned in settings rows; label wraps. |
When to use
- Preference toggles that save instantly
When not to use
- Inside a form that has a Save button — use Checkbox
Accessibility
- `role="switch"` with `aria-checked` via Radix.
- Pointer target ≥24px (≥44px on coarse pointers) via `ds-target`.
- Visible `ds-focus-ring` on `:focus-visible`; never focus-suppressed.
CheckboxGroup
Labels a set of related checkboxes with shared helper and error messaging.
CheckboxGroupLive component
Select all that apply.
Legacy mapping
Legacy multi-choice blocks repeated a bold paragraph as a pseudo-label with no programmatic grouping. V2 makes the group a real labelled group.
| Attribute | Specification |
|---|---|
| Default | orientation="vertical" |
| Variants | vertical · horizontal |
| Sizes | Inherited from child checkboxes |
| States | default · required · error |
| Icon behaviour | None at group level. |
| Spacing | gap-2 vertical; gap-x-6 / gap-y-2 horizontal. |
| Typography | Group label `text-label-sm`; messages `text-caption`. |
| Border radius | n/a |
| Colour usage | Error message uses `text-status-error`. |
| Responsive | Horizontal collapses to vertical when the options wrap. |
When to use
- "Select all that apply" question blocks
When not to use
- A single standalone checkbox
Accessibility
- `role="group"` + `aria-labelledby`, one shared `aria-describedby` for helper/error.
SelectionCard
A card-sized choice with title, description, icon and metadata.
SelectionCardSelectionCardGroupLive component
You can change this later in settings.
Legacy mapping
Legacy plan/role pickers were clickable divs with no keyboard support. V2 renders real radio/checkbox semantics behind the card surface.
| Attribute | Specification |
|---|---|
| Default | mode="single", size="medium", columns=1 |
| Variants | single (radio) · multiple (checkbox) |
| Sizes | small (12px padding) · medium (16px padding) |
| States | default · hover · focus-visible · selected · disabled · error |
| Icon behaviour | Optional leading icon at `--size-icon-md`; the indicator is drawn from the same radio/checkbox vocabulary. |
| Spacing | p-4, gap-3, grid gap-3 between cards. |
| Typography | Title `text-label-md`, description `text-body-sm`. |
| Border radius | `rounded-md`. |
| Colour usage | Selected uses `border-primary` + `bg-primary-subtle`. |
| Responsive | `columns` 2/3 collapse to one column below `sm`. |
When to use
- Plan, role or workflow choices that need explanation
When not to use
- Short option lists — use Radio
- Navigation — use a link card
Accessibility
- Radix Radio/Checkbox roots — full keyboard and ARIA parity with plain controls.
- Card labelled by its title and described by its description.