Files
ADS3-Design-System/plans/toggle-controls.md
Richie 07be9d7314 Add Input, Checkbox, Radio, and Switch form components with semantic token layer
Build four form primitives from Figma references with brand-aligned creative
decisions: restrained press states (scale-95 instead of highlight splashes),
clean iconless Switch, and consistent error states with inline warning icons.

Introduce form-control semantic tokens (--color-control-*) in tokens.css so
all form components share a single source for borders, checked states, focus
rings, labels, and errors. Retrofit Input to use these tokens instead of
direct palette references.

Update CLAUDE.md and ARCHITECTURE.md with token layer documentation, token
discipline rule (no palette references in components), and component tier
decision criteria.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-21 14:00:56 +10:00

91 lines
2.9 KiB
Markdown

# Toggle Controls: Checkbox, Radio, Switch
## Figma References
- Checkbox: `node-id=33-5043`
- Radio: `node-id=33-5188`
- Switch: `node-id=33-5337`
## What I'm Taking from Figma
- **Sizing**: 20px checkbox/radio icon inside a clickable area, 16px body text labels
- **Colours**: blue-01 (`#002664`) for checked state, grey-03 for unchecked border, grey-01 for labels
- **Layout**: icon + label in a horizontal row with 8px gap
- **Disabled**: 60% opacity
## What I'm Changing (Per Your Brief)
### Pressed/Active States (Checkbox & Radio)
The Figma designs use a large circular background highlight on press — feels heavy for a form control. Instead:
- **Hover**: subtle border colour shift to blue-01 (unchecked) or slight darkening (checked)
- **Active/pressed**: brief scale-down (`scale-95`) on the control — tactile without a big splashy highlight
- **Focus-visible**: 2px blue-04 ring offset for keyboard nav (accessibility)
### Switch
Figma has icons inside the thumb (check/X). Removing those per your request. My approach:
- **Track**: 44px wide x 24px tall, rounded-full. Off = grey-03, On = blue-01
- **Thumb**: 18px white circle, smooth slide transition (150ms)
- **Hover**: track lightens/darkens slightly
- **Focus-visible**: ring around the track
- Clean, minimal look that matches the Input component's rounded-[4px] + blue-01 brand
### Indeterminate (Checkbox Only)
Keeping the indeterminate state (dash icon) — useful for "select all" patterns. Same blue-01 fill as checked.
## Props
### Checkbox
```ts
interface CheckboxProps {
label?: string
description?: string // secondary text below label
checked?: boolean
indeterminate?: boolean
disabled?: boolean
error?: string
onChange?: (checked: boolean) => void
}
```
### Radio
```ts
interface RadioProps {
label?: string
description?: string
value: string
disabled?: boolean
}
interface RadioGroupProps {
label?: string // group legend
description?: string
value?: string
defaultValue?: string
error?: string
disabled?: boolean
orientation?: 'vertical' | 'horizontal'
onChange?: (value: string) => void
children: React.ReactNode // Radio items
}
```
### Switch
```ts
interface SwitchProps {
label?: string
description?: string
checked?: boolean
disabled?: boolean
onChange?: (checked: boolean) => void
}
```
## Accessibility
- Checkbox: hidden native `<input type="checkbox">` with visual overlay, `aria-checked`, supports indeterminate
- Radio: native `<input type="radio">` within a `<fieldset>`/`<legend>` group, arrow key navigation
- Switch: `role="switch"` with `aria-checked`, toggled via Space/Enter
- All: focus-visible ring, disabled state, proper label association
## Stories (per component)
- Default, Checked, WithDescription, Disabled, DisabledChecked, WithError, AllStates
- Radio adds: RadioGroup, Horizontal, WithError
- Switch adds: Default, On, WithDescription, Disabled, AllStates