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>
This commit is contained in:
90
plans/toggle-controls.md
Normal file
90
plans/toggle-controls.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user