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

Button

V2

Performs an action. The single action control of the system.

ButtonbuttonVariants

Live component

Legacy mapping

Legacy screens used bespoke blue/grey/outline/link buttons with three different heights. V2 maps them onto primary / secondary / tertiary / ghost / destructive / link at three fixed heights.

Button — standardized specification
AttributeSpecification
Defaultvariant="primary", size="medium"
Variantsprimary · secondary · tertiary · ghost · destructive · link
Sizessmall (32px) · medium (40px) · large (48px)
Statesdefault · hover · active · focus-visible · disabled · loading
Icon behaviour`leadingIcon` / `trailingIcon` accept a Lucide icon; size follows the button size token (12/16/20px). Icon-only goes to IconButton.
Spacinggap-1 / gap-2 by size; horizontal padding 12 / 16 / 24px.
TypographyNunito, `text-label-sm|md|lg`, weight 500.
Border radius`rounded-sm` (small) / `rounded-md` (medium, large).
Colour usageSemantic only: `bg-primary`, `bg-error`, `text-primary-foreground`; disabled uses the explicit disabled tokens, never opacity alone.
ResponsiveFull-width via `className="w-full"` in stacked mobile forms; label never truncates — wrap the container instead.

When to use

  • Submitting, creating, exporting, deleting or continuing
  • The single primary action of a screen or dialog

When not to use

  • Navigation to another page — use a link
  • Selecting a value — use SegmentedControl, ToggleGroup or a radio

Accessibility

  • Real `<button>`; `asChild` only for links that look like buttons.
  • `loading` sets `aria-busy` and keeps an announced loading label.
  • Visible `ds-focus-ring` on `:focus-visible`; never focus-suppressed.
  • Pointer target ≥24px (≥44px on coarse pointers) via `ds-target`.

ButtonGroup

V2

Groups related actions as one attached control or a spaced toolbar.

ButtonGroup

Live component

Legacy mapping

Legacy toolbars hand-assembled adjoining buttons with negative margins per screen. V2 provides the joining rules once.

ButtonGroup — standardized specification
AttributeSpecification
Defaultvariant="attached", orientation="horizontal"
Variantsattached · spaced
SizesInherited from the child Buttons
StatesPer child button; focused child raises above its neighbours
Icon behaviourChildren keep their own icon rules; mixing icon-only and labelled children is allowed.
Spacingattached: -1px overlap so borders never double. spaced: `gap-2`.
TypographyInherited from children.
Border radiusOuter corners `rounded-md`; inner corners square.
Colour usageInherited; keep one weight per group so no child out-shouts the rest.
ResponsiveSwitch to `orientation="vertical"` or `spaced` below `sm`.

When to use

  • Split actions
  • A compact toolbar of sibling actions

When not to use

  • Mutually exclusive selection — use SegmentedControl

Accessibility

  • `role="group"` with an `aria-label` when the grouping carries meaning.
  • Focus order follows DOM order; the focused child is raised so its ring is unclipped.

Badge / Tag

V2

Badge labels status, Tag labels a categorical attribute, Chip is an interactive/removable filter value.

BadgeTagChipChipGroup

Live component

DraftActiveReview dueOverdueScheduled
Dementia careNight shift

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

Badge / Tag — standardized specification
AttributeSpecification
DefaultBadge variant="neutral", size="medium"
Variantsneutral · primary · success · warning · error · info · outline
Sizessmall · medium
Statesdefault · hover / focus (Chip only) · selected (Chip) · removable (Chip)
Icon behaviourOptional leading status dot or Lucide icon at `--size-icon-xs`; remove affordance is a labelled control.
Spacingpx-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.
ResponsiveWraps 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

V2

Independent on/off choice, with tri-state support for parent rows.

Checkbox

Live component

They receive a read-only summary.

Legacy mapping

Legacy checkboxes came in two sizes with an inconsistent indeterminate glyph. V2 fixes two sizes and a single Minus/Check glyph pair.

Checkbox — standardized specification
AttributeSpecification
Defaultsize="medium" (20px box)
Variantsunchecked · checked · indeterminate
Sizessmall (16px) · medium (20px)
Statesdefault · hover · focus-visible · checked · indeterminate · disabled · error
Icon behaviourCheck / Minus at `--size-icon-xs`, stroke 3, `primary-foreground`.
Spacinggap-2 to the label; helper text indents to the label edge.
TypographyLabel `text-body-sm`; helper/error `text-caption`.
Border radius`rounded-xs`.
Colour usage`bg-primary` + `border-primary` when checked; error borders use `border-error`.
ResponsiveLabel 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

V2

One choice from a small, always-visible set.

RadioRadioGroupRadioField

Live component

Shift pattern(required)
Same hours every week.
Alternates weekly.
Ad-hoc availability.

Legacy mapping

Legacy radios were sometimes styled buttons. V2 keeps real radios and routes button-like exclusive choices to SegmentedControl.

Radio — standardized specification
AttributeSpecification
Defaultsize="medium"
Variantswith label · with label + description
Sizessmall (16px) · medium (20px)
Statesdefault · hover · focus-visible · checked · disabled · error
Icon behaviourNo icon; a filled dot indicates selection.
Spacinggap-2 to label, gap-2 between options.
TypographyLabel `text-body-sm`, description `text-caption`.
Border radius`rounded-full`.
Colour usage`border-primary` + `bg-primary` dot.
ResponsiveVertical 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

V2

Immediately-applied on/off setting.

Switch

Live component

Applies immediately.

Legacy mapping

Legacy settings mixed switches and checkboxes for the same behaviour. V2 rule: Switch = applies immediately; Checkbox = applies on save.

Switch — standardized specification
AttributeSpecification
Defaultsize="medium"
Variantswith label · label + description
Sizessmall · medium
Statesoff · on · hover · focus-visible · disabled
Icon behaviourNo icon inside the track; pair with a status text if the effect is not obvious.
Spacinggap-3 between control and label block.
TypographyLabel `text-label-md`, description `text-body-sm`.
Border radius`rounded-full`.
Colour usage`bg-primary` when on, `bg-border-strong` when off.
ResponsiveControl 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

V2

Labels a set of related checkboxes with shared helper and error messaging.

CheckboxGroup

Live component

Notify me about(required)

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.

CheckboxGroup — standardized specification
AttributeSpecification
Defaultorientation="vertical"
Variantsvertical · horizontal
SizesInherited from child checkboxes
Statesdefault · required · error
Icon behaviourNone at group level.
Spacinggap-2 vertical; gap-x-6 / gap-y-2 horizontal.
TypographyGroup label `text-label-sm`; messages `text-caption`.
Border radiusn/a
Colour usageError message uses `text-status-error`.
ResponsiveHorizontal 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

V2

A card-sized choice with title, description, icon and metadata.

SelectionCardSelectionCardGroup

Live component

Workspace type

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.

SelectionCard — standardized specification
AttributeSpecification
Defaultmode="single", size="medium", columns=1
Variantssingle (radio) · multiple (checkbox)
Sizessmall (12px padding) · medium (16px padding)
Statesdefault · hover · focus-visible · selected · disabled · error
Icon behaviourOptional leading icon at `--size-icon-md`; the indicator is drawn from the same radio/checkbox vocabulary.
Spacingp-4, gap-3, grid gap-3 between cards.
TypographyTitle `text-label-md`, description `text-body-sm`.
Border radius`rounded-md`.
Colour usageSelected 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.