@hegemonart/get-design-done 1.16.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +12 -4
- package/.claude-plugin/plugin.json +22 -4
- package/CHANGELOG.md +111 -0
- package/README.md +27 -2
- package/agents/design-auditor.md +65 -1
- package/agents/design-context-builder.md +6 -1
- package/agents/design-doc-writer.md +21 -0
- package/agents/design-executor.md +22 -4
- package/agents/design-pattern-mapper.md +62 -0
- package/agents/design-phase-researcher.md +1 -1
- package/agents/motion-mapper.md +74 -9
- package/agents/token-mapper.md +8 -0
- package/package.json +16 -2
- package/reference/components/README.md +27 -23
- package/reference/components/alert.md +198 -0
- package/reference/components/badge.md +202 -0
- package/reference/components/breadcrumbs.md +198 -0
- package/reference/components/chip.md +209 -0
- package/reference/components/command-palette.md +228 -0
- package/reference/components/date-picker.md +227 -0
- package/reference/components/file-upload.md +219 -0
- package/reference/components/list.md +217 -0
- package/reference/components/menu.md +212 -0
- package/reference/components/navbar.md +211 -0
- package/reference/components/pagination.md +205 -0
- package/reference/components/progress.md +210 -0
- package/reference/components/rich-text-editor.md +226 -0
- package/reference/components/sidebar.md +211 -0
- package/reference/components/skeleton.md +197 -0
- package/reference/components/slider.md +208 -0
- package/reference/components/stepper.md +220 -0
- package/reference/components/table.md +229 -0
- package/reference/components/toast.md +200 -0
- package/reference/components/tree.md +225 -0
- package/reference/css-grid-layout.md +835 -0
- package/reference/data-visualization.md +333 -0
- package/reference/external/NOTICE.hyperframes +28 -0
- package/reference/form-patterns.md +245 -0
- package/reference/image-optimization.md +582 -0
- package/reference/information-architecture.md +255 -0
- package/reference/motion-advanced.md +754 -0
- package/reference/motion-easings.md +381 -0
- package/reference/motion-interpolate.md +282 -0
- package/reference/motion-spring.md +234 -0
- package/reference/motion-transition-taxonomy.md +155 -0
- package/reference/motion.md +20 -0
- package/reference/onboarding-progressive-disclosure.md +250 -0
- package/reference/output-contracts/motion-map.schema.json +135 -0
- package/reference/platforms.md +346 -0
- package/reference/registry.json +445 -220
- package/reference/registry.schema.json +4 -0
- package/reference/rtl-cjk-cultural.md +353 -0
- package/reference/user-research.md +360 -0
- package/reference/variable-fonts-loading.md +532 -0
- package/scripts/lib/easings.cjs +280 -0
- package/scripts/lib/parse-contract.cjs +220 -0
- package/scripts/lib/spring.cjs +160 -0
- package/scripts/tests/test-motion-provenance.sh +64 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Skeleton — Benchmark Spec
|
|
2
|
+
|
|
3
|
+
**Harvested from**: Polaris, Carbon, Atlassian, Mantine
|
|
4
|
+
**Wave**: 3 · **Category**: Feedback
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
A skeleton screen is a loading placeholder that mirrors the shape of the content it will replace — text lines, images, cards, avatars. It reduces perceived wait time by showing the structural layout before real data arrives, preventing the jarring reflow that occurs when content suddenly appears. Use skeleton when the content shape is known; use an indeterminate spinner or progress bar when shape is unknown. *(Polaris, Carbon, Atlassian, Mantine agree: skeleton = shape-matched placeholder, not generic spinner)*
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Anatomy
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
┌──────────────────────────────────┐
|
|
18
|
+
│ ████ (avatar-circle, 40px) │ ← aria-hidden="true"
|
|
19
|
+
│ ██████████████████ (text-line) │
|
|
20
|
+
│ █████████████ (text-line 75%) │
|
|
21
|
+
│ ████████████████████ (text-line)│
|
|
22
|
+
└──────────────────────────────────┘
|
|
23
|
+
↑ container: aria-busy="true" aria-label="Loading…"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Part | Required | Notes |
|
|
27
|
+
|------|----------|-------|
|
|
28
|
+
| Container | Yes | `aria-busy="true"` + `aria-label="Loading…"` or `aria-labelledby` |
|
|
29
|
+
| Skeleton elements | Yes | `aria-hidden="true"` on each shape element |
|
|
30
|
+
| Text-line shape | No | Width 60–90% (varied) to mimic text flow |
|
|
31
|
+
| Image/card block | No | Fixed aspect-ratio; fills the same space as the loaded image |
|
|
32
|
+
| Avatar circle | No | Circular shape; diameter matches the final avatar size |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Variants
|
|
37
|
+
|
|
38
|
+
| Variant | Description | Systems |
|
|
39
|
+
|---------|-------------|---------|
|
|
40
|
+
| Text lines | One or more lines at varying widths (60–90%) | All |
|
|
41
|
+
| Image block | Rectangular/aspect-ratio shape for images | Polaris, Carbon, Mantine |
|
|
42
|
+
| Avatar | Circular placeholder at avatar diameter | Polaris, Atlassian, Mantine |
|
|
43
|
+
| Card | Full card-sized block with text-line children | All |
|
|
44
|
+
| Table row | Row-shaped block with column-width children | Carbon, Mantine |
|
|
45
|
+
|
|
46
|
+
**Norm** (≥4/18 systems agree): shimmer animation (left-to-right gradient sweep); vary text-line widths 60–90%; `aria-hidden` on skeleton elements; `aria-busy` on container.
|
|
47
|
+
**Diverge**: Polaris renders skeleton as named sub-components (`SkeletonBodyText`, `SkeletonDisplayText`); Carbon uses CSS class modifiers; Mantine uses a generic `Skeleton` with width/height props; Atlassian uses a shape prop.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## States
|
|
52
|
+
|
|
53
|
+
| State | Trigger | Visual | ARIA |
|
|
54
|
+
|-------|---------|--------|------|
|
|
55
|
+
| loading | data fetch in progress | Shimmer animation looping | `aria-busy="true"` on container |
|
|
56
|
+
| loaded | data resolves | Skeleton removed, real content appears | `aria-busy="false"` or remove attr |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Sizing & Spacing
|
|
61
|
+
|
|
62
|
+
| Shape | Default sizing | Notes |
|
|
63
|
+
|-------|----------------|-------|
|
|
64
|
+
| Text-line | 16px height | Matches body line-height slot |
|
|
65
|
+
| Text-line (heading) | 24–28px height | Matches h2/h3 slot |
|
|
66
|
+
| Avatar | Match target avatar diameter | 32px, 40px, 48px common |
|
|
67
|
+
| Image block | Match target image aspect ratio | 16:9, 1:1, 4:3 common |
|
|
68
|
+
| Gap between text lines | 8px | Matches body line-height rhythm |
|
|
69
|
+
|
|
70
|
+
**Norm**: Match exact pixel dimensions of the content being replaced — layout shift score is zero when skeleton matches final content size *(Polaris, Mantine)*.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Typography
|
|
75
|
+
|
|
76
|
+
Skeleton shapes are purely visual — no typography content. However:
|
|
77
|
+
- Text-line height should match the `line-height` of the real text it replaces
|
|
78
|
+
- Heading skeleton height should match the heading's `font-size` + leading
|
|
79
|
+
- Do NOT use placeholder text ("Loading…") inside skeleton shapes — use `aria-label` on the container instead
|
|
80
|
+
|
|
81
|
+
Cross-link: `reference/typography.md` — line-height scale for matching skeleton dimensions
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Keyboard & Accessibility
|
|
86
|
+
|
|
87
|
+
> **WAI-ARIA role**: no role on skeleton shapes; container uses `aria-busy`
|
|
88
|
+
> **Required attributes**: `aria-hidden="true"` on each skeleton element; `aria-busy="true"` + `aria-label="Loading…"` on container
|
|
89
|
+
|
|
90
|
+
### Keyboard Contract
|
|
91
|
+
|
|
92
|
+
*Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/ — W3C — 2024*
|
|
93
|
+
|
|
94
|
+
| Key | Action |
|
|
95
|
+
|-----|--------|
|
|
96
|
+
| (none) | Skeleton shapes are not interactive; no keyboard interaction |
|
|
97
|
+
|
|
98
|
+
Skeleton elements must not receive focus. The container is not focusable unless it wraps focusable content that appears after loading.
|
|
99
|
+
|
|
100
|
+
### Accessibility Rules
|
|
101
|
+
|
|
102
|
+
- Every skeleton shape element MUST have `aria-hidden="true"` — blank shapes announced by screen readers ("image", "text") confuse users *(Polaris, Carbon)*
|
|
103
|
+
- The container MUST have `aria-busy="true"` while loading, set to `false` (or removed) when content appears *(WAI-ARIA APG)*
|
|
104
|
+
- The container MUST have `aria-label="Loading…"` or equivalent — this is what screen readers announce while `aria-busy="true"` *(WAI-ARIA APG)*
|
|
105
|
+
- Do NOT use skeleton as the only loading indicator for screen reader users — announce loading state via live region if the transition is programmatic *(Carbon)*
|
|
106
|
+
- Shimmer animation MUST be suppressed under `prefers-reduced-motion` — use static fill instead *(WCAG 2.3.3)*
|
|
107
|
+
|
|
108
|
+
Cross-link: `reference/accessibility.md` — `aria-busy`, `prefers-reduced-motion`
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Motion
|
|
113
|
+
|
|
114
|
+
| Transition | Duration | Easing | Notes |
|
|
115
|
+
|------------|----------|--------|-------|
|
|
116
|
+
| Shimmer sweep | 1.5s | ease-in-out | 130° gradient: transparent → surface-highlight → transparent |
|
|
117
|
+
| Loop delay | 0.5s | — | Pause between sweeps to avoid strobing |
|
|
118
|
+
| Skeleton → content | 200ms | ease-out | Fade-in real content over skeleton |
|
|
119
|
+
|
|
120
|
+
Shimmer gradient direction: 130 degrees (roughly top-left to bottom-right) — matches natural reading direction.
|
|
121
|
+
Background: `surface-variant` token (slightly darker than background surface, lighter than border).
|
|
122
|
+
|
|
123
|
+
**BAN**: High-contrast shimmer (e.g. white → gray on dark) — too visually noisy. Do NOT use spinner animation inside a skeleton shape. Under `prefers-reduced-motion`, remove the sweep entirely and use static fill.
|
|
124
|
+
|
|
125
|
+
Cross-link: `reference/motion.md` — `prefers-reduced-motion`: static fill, no gradient sweep
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Do / Don't
|
|
130
|
+
|
|
131
|
+
### Do
|
|
132
|
+
- Match skeleton dimensions exactly to target content to avoid layout shift *(Polaris, Mantine)*
|
|
133
|
+
- Vary text-line widths 60–90% to simulate natural text flow *(Carbon, Atlassian)*
|
|
134
|
+
- Set `aria-hidden="true"` on every skeleton shape element *(WAI-ARIA APG, Polaris)*
|
|
135
|
+
- Set `aria-busy="true"` + `aria-label="Loading…"` on the container *(WAI-ARIA APG)*
|
|
136
|
+
|
|
137
|
+
### Don't
|
|
138
|
+
- Don't use a spinner when content shape is known — skeleton is always preferred for layout-bearing slots *(Carbon, Polaris)*
|
|
139
|
+
- Don't use strong contrast for shimmer — use `surface-variant` (low contrast) *(Atlassian, Mantine)*
|
|
140
|
+
- Don't animate shimmer under `prefers-reduced-motion` — use static fill *(WCAG 2.3.3)*
|
|
141
|
+
- Don't add visible "Loading…" text inside skeleton shapes — put it on the container via `aria-label` *(Carbon)*
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Anti-patterns Cross-links
|
|
146
|
+
|
|
147
|
+
| Anti-pattern | Entry |
|
|
148
|
+
|--------------|-------|
|
|
149
|
+
| Spinner used when content shape is known | `reference/anti-patterns.md#ban-spinner-overuse` |
|
|
150
|
+
| Missing aria-hidden on visual-only elements | `reference/anti-patterns.md#ban-aria-hidden` |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Benchmark Citations
|
|
155
|
+
|
|
156
|
+
| Claim | Sources |
|
|
157
|
+
|-------|---------|
|
|
158
|
+
| aria-hidden="true" on skeleton shapes | WAI-ARIA APG, Polaris, Carbon |
|
|
159
|
+
| aria-busy="true" on container | WAI-ARIA APG |
|
|
160
|
+
| Text-line widths 60–90% varied | Carbon, Atlassian, Mantine |
|
|
161
|
+
| Shimmer 130° gradient sweep 1.5s | Polaris, Mantine |
|
|
162
|
+
| Suppress shimmer under prefers-reduced-motion | WCAG 2.3.3 |
|
|
163
|
+
| Match exact target dimensions to prevent layout shift | Polaris, Mantine |
|
|
164
|
+
|
|
165
|
+
Full system URLs: `connections/design-corpora.md`
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Grep Signatures
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Skeleton shapes missing aria-hidden
|
|
173
|
+
grep -rn 'skeleton\|Skeleton' src/ | grep -v 'aria-hidden="true"' | grep -v 'aria-busy\|container'
|
|
174
|
+
|
|
175
|
+
# Skeleton container missing aria-busy
|
|
176
|
+
grep -rn 'skeleton\|Skeleton' src/ | grep 'container\|wrapper\|section' | grep -v 'aria-busy'
|
|
177
|
+
|
|
178
|
+
# Shimmer animation without prefers-reduced-motion guard
|
|
179
|
+
grep -rn 'shimmer\|skeleton.*animation\|@keyframes.*skeleton' src/ | grep -v 'prefers-reduced-motion'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Failing Example
|
|
185
|
+
|
|
186
|
+
```html
|
|
187
|
+
<!-- BAD: skeleton shapes with no aria-hidden, container with no aria-busy -->
|
|
188
|
+
<div class="card-skeleton">
|
|
189
|
+
<div class="skeleton-avatar"></div>
|
|
190
|
+
<div class="skeleton-line skeleton-line--80"></div>
|
|
191
|
+
<div class="skeleton-line skeleton-line--60"></div>
|
|
192
|
+
</div>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Why it fails**: Screen readers traverse the skeleton shapes and announce them as empty elements ("image", unlabeled regions). There is no `aria-busy="true"` to signal a loading state. No `aria-label` tells the user what is loading. When content loads, no `aria-busy="false"` transition signals completion.
|
|
196
|
+
**Grep detection**: `grep -rn 'skeleton' src/ | grep -v 'aria-hidden\|aria-busy'`
|
|
197
|
+
**Fix**: Add `aria-busy="true" aria-label="Loading card content"` to the container; add `aria-hidden="true"` to every skeleton child shape.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Slider — Benchmark Spec
|
|
2
|
+
|
|
3
|
+
**Harvested from**: WAI-ARIA APG Slider pattern, Material 3, Radix Slider, Carbon Design System
|
|
4
|
+
**Wave**: 5 · **Category**: Advanced
|
|
5
|
+
**Spec file**: `reference/components/slider.md`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
A Slider lets users select a numeric value (or range of values) by dragging a thumb along a track. It is appropriate when the range of values is meaningful as a continuum — volume, price, opacity, temperature — and the exact numeric value matters less than relative position. For precise numeric entry, pair the slider with a text input. For a range, use two thumbs. *(Material 3, Carbon, Radix agree: slider = continuous or discrete value selection with visual track.)*
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Single:
|
|
19
|
+
[ ●────────────────────── ] ← track
|
|
20
|
+
thumb
|
|
21
|
+
|
|
22
|
+
Range:
|
|
23
|
+
[ ──────●────────●──────── ]
|
|
24
|
+
min max
|
|
25
|
+
thumb thumb
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Part | Required | Notes |
|
|
29
|
+
|------|----------|-------|
|
|
30
|
+
| Track | Yes | Full-width bar; inactive segments use muted color |
|
|
31
|
+
| Active range fill | Yes | Filled portion between min-edge and thumb (single) or between thumbs (range) |
|
|
32
|
+
| Thumb | Yes | Draggable handle; visual ≥12px; touch target ≥44px via ::before padding |
|
|
33
|
+
| Value label (tooltip) | No | Shows current value above/beside thumb on interaction |
|
|
34
|
+
| Tick marks | No | Shown for discrete steps when ≤10 steps |
|
|
35
|
+
| Min/Max labels | No | Static text at track ends |
|
|
36
|
+
| Numeric input | No | Paired `<input type="number">` for precise entry |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Variants
|
|
41
|
+
|
|
42
|
+
| Variant | Description | Systems |
|
|
43
|
+
|---------|-------------|---------|
|
|
44
|
+
| Single | One thumb; selects a scalar value | WAI-ARIA APG, Material 3, Carbon, Radix |
|
|
45
|
+
| Range | Two thumbs; selects min and max of a range | Material 3 (RangeSlider), Carbon, Radix |
|
|
46
|
+
| Discrete | Step-snapping with tick marks (≤10 steps) | Material 3, Carbon |
|
|
47
|
+
| Continuous | No snapping; free movement | Material 3, Carbon, Radix |
|
|
48
|
+
| Vertical | `aria-orientation="vertical"`; track runs top-to-bottom | WAI-ARIA APG, Carbon |
|
|
49
|
+
|
|
50
|
+
**Norm** (≥3/4 systems agree): horizontal orientation is default; thumb is a circle on a horizontal track; active range fills in brand color.
|
|
51
|
+
**Diverge**: Carbon shows tick labels below the track; Material 3 shows a floating value tooltip on drag; Radix delegates tooltip entirely to the consumer.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## States
|
|
56
|
+
|
|
57
|
+
| State | Trigger | Visual | ARIA |
|
|
58
|
+
|-------|---------|--------|------|
|
|
59
|
+
| default | — | Track + thumb at resting position | `aria-valuenow` reflects current value |
|
|
60
|
+
| hover | Pointer over thumb | Thumb expands or shows halo | — |
|
|
61
|
+
| focus | Keyboard focus on thumb | Focus-visible ring on thumb | — |
|
|
62
|
+
| dragging / active | Mousedown / touch on thumb | Value tooltip visible; thumb slightly enlarged | — |
|
|
63
|
+
| disabled | `disabled` / `aria-disabled` | 38% opacity; cursor: not-allowed | `aria-disabled="true"` |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Sizing & Spacing
|
|
68
|
+
|
|
69
|
+
| Size | Track Height | Thumb Visual | Thumb Hit Area | Notes |
|
|
70
|
+
|------|-------------|--------------|----------------|-------|
|
|
71
|
+
| sm | 2px | 12px | 44px (via ::before) | Compact; pair with numeric input |
|
|
72
|
+
| md (default) | 4px | 20px | 44px (via ::before) | Standard; tick labels at 14px |
|
|
73
|
+
| lg | 6px | 24px | 44px | High-emphasis; price/volume controls |
|
|
74
|
+
|
|
75
|
+
**Norm**: 4px track height (Material 3, Carbon). Thumb visual diameter 20px with ::before/::after expanding the touch target to ≥44px without inflating layout.
|
|
76
|
+
|
|
77
|
+
Cross-link: `reference/surfaces.md` — 44×44px touch-target minimum; use padding trick, not enlarged visual.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Typography
|
|
82
|
+
|
|
83
|
+
- Value tooltip: caption-sm, weight 500, centered above thumb
|
|
84
|
+
- Tick labels: caption-xs, secondary color, centered below tick mark
|
|
85
|
+
- Min/Max endpoint labels: caption-sm, secondary color, flush with track ends
|
|
86
|
+
|
|
87
|
+
Cross-link: `reference/typography.md` — tabular-nums on value tooltip so digits don't shift width during drag.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Keyboard & Accessibility
|
|
92
|
+
|
|
93
|
+
> **WAI-ARIA role**: `slider`
|
|
94
|
+
> **Required attributes**: `aria-valuenow`, `aria-valuemin`, `aria-valuemax`; `aria-valuetext` for human-readable value (e.g., "$45" or "45%"); `aria-label` or `aria-labelledby`; `aria-orientation="vertical"` when vertical
|
|
95
|
+
|
|
96
|
+
### Keyboard Contract
|
|
97
|
+
|
|
98
|
+
*Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/slider/ — W3C — 2024*
|
|
99
|
+
|
|
100
|
+
| Key | Action |
|
|
101
|
+
|-----|--------|
|
|
102
|
+
| Right Arrow / Up Arrow | Increase value by one step |
|
|
103
|
+
| Left Arrow / Down Arrow | Decrease value by one step |
|
|
104
|
+
| Page Up | Increase value by a larger step (typically 10% of range) |
|
|
105
|
+
| Page Down | Decrease value by a larger step (typically 10% of range) |
|
|
106
|
+
| Home | Set value to minimum |
|
|
107
|
+
| End | Set value to maximum |
|
|
108
|
+
|
|
109
|
+
*For vertical slider (`aria-orientation="vertical"`), Up Arrow increases and Down Arrow decreases.*
|
|
110
|
+
|
|
111
|
+
### Accessibility Rules
|
|
112
|
+
|
|
113
|
+
- Every slider thumb MUST have `aria-valuenow`; update it continuously during drag
|
|
114
|
+
- `aria-valuetext` MUST be provided when raw number is not human-readable (e.g., "Low", "Medium", "High" for a quality setting, or "$45" for a price)
|
|
115
|
+
- Range slider: label each thumb distinctly (e.g., `aria-label="Minimum price"` and `aria-label="Maximum price"`) — identical labels confuse screen readers
|
|
116
|
+
- Thumb touch target MUST be ≥44×44px; use `::before`/`::after` pseudo-element padding if visual thumb is smaller
|
|
117
|
+
- Do not use `<input type="range">` hidden behind a custom div without ARIA — the native element is preferable when no custom styling is required
|
|
118
|
+
- Disabled sliders: use `aria-disabled="true"`; keep thumb in tab order so AT can announce the current value
|
|
119
|
+
|
|
120
|
+
Cross-link: `reference/accessibility.md` — slider role, aria-valuetext guidance.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Motion
|
|
125
|
+
|
|
126
|
+
| Transition | Duration | Easing | Notes |
|
|
127
|
+
|------------|----------|--------|-------|
|
|
128
|
+
| Thumb drag | 0ms | — | No easing on drag — follows pointer exactly |
|
|
129
|
+
| Keyboard step | 80ms | ease-out | Smooth snap to new position |
|
|
130
|
+
| Value tooltip appear | 100ms | ease-out | Fade in on focus/drag |
|
|
131
|
+
| Value tooltip dismiss | 150ms | ease-in | Fade out on blur |
|
|
132
|
+
| Tick mark appear | 120ms | ease-out | When switching to discrete mode |
|
|
133
|
+
|
|
134
|
+
**BAN**: Do not add momentum or inertia easing to thumb drag — feels broken and breaks accessibility (thumb does not match pointer).
|
|
135
|
+
|
|
136
|
+
Cross-link: `reference/motion.md` — reduced-motion: remove keyboard-step animation; thumb jumps instantly.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Do / Don't
|
|
141
|
+
|
|
142
|
+
### Do
|
|
143
|
+
- Provide `aria-valuetext` with a human-readable label when the raw number needs context *(WAI-ARIA APG)*
|
|
144
|
+
- Give each range thumb a unique, descriptive `aria-label` *(WAI-ARIA APG, Radix Slider docs)*
|
|
145
|
+
- Expand thumb touch target to ≥44px with pseudo-element padding *(Material 3, Carbon)*
|
|
146
|
+
- Show tick marks only for discrete sliders with ≤10 steps *(Material 3, Carbon)*
|
|
147
|
+
|
|
148
|
+
### Don't
|
|
149
|
+
- Don't build a slider from `<div>` with mouse events only — no keyboard, no ARIA *(diverges from all 4 systems)*
|
|
150
|
+
- Don't omit `aria-valuenow` updates during drag — AT users hear a stale value *(WAI-ARIA APG)*
|
|
151
|
+
- Don't let the visual thumb area be smaller than 12px with no hit-area expansion — fails WCAG 2.5.8 *(Material 3)*
|
|
152
|
+
- Don't use identical `aria-label` for both range thumbs *(WAI-ARIA APG)*
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Anti-patterns Cross-links
|
|
157
|
+
|
|
158
|
+
| Anti-pattern | Entry |
|
|
159
|
+
|--------------|-------|
|
|
160
|
+
| BAN-09 | Custom interactive widget with mouse events only, no keyboard — `reference/anti-patterns.md#ban-09` |
|
|
161
|
+
| BAN-11 | Touch target below 44px without padding expansion — `reference/anti-patterns.md#ban-11` |
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Benchmark Citations
|
|
166
|
+
|
|
167
|
+
| Claim | Sources |
|
|
168
|
+
|-------|---------|
|
|
169
|
+
| role="slider" + aria-valuenow/min/max required | WAI-ARIA APG Slider pattern |
|
|
170
|
+
| Arrow key step, Page key 10% step, Home/End to extremes | WAI-ARIA APG keyboard contract |
|
|
171
|
+
| 4px track height default | Material 3, Carbon |
|
|
172
|
+
| Thumb touch target ≥44px via pseudo-element | Material 3, Carbon accessibility guidelines |
|
|
173
|
+
| aria-valuetext for non-numeric human-readable values | WAI-ARIA APG, Radix Slider docs |
|
|
174
|
+
|
|
175
|
+
Full system URLs: `connections/design-corpora.md`
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Grep Signatures
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Slider thumb missing aria-valuenow (ARIA contract violation)
|
|
183
|
+
grep -rn 'role="slider"' src/ | grep -v 'aria-valuenow'
|
|
184
|
+
|
|
185
|
+
# Custom slider div with mouse events only — no keyboard handler
|
|
186
|
+
grep -rn 'class.*slider\|\.slider' src/ | grep 'onMouseDown\|mousedown' | grep -v 'onKeyDown\|keydown\|role="slider"'
|
|
187
|
+
|
|
188
|
+
# Thumb element potentially below 44px with no hit-area expansion
|
|
189
|
+
grep -rn '\.thumb\|slider-thumb' src/ | grep 'width:\s*[0-9]\{1,2\}px\|height:\s*[0-9]\{1,2\}px' | grep -v '::before\|::after\|padding'
|
|
190
|
+
|
|
191
|
+
# Range slider thumbs with identical aria-label
|
|
192
|
+
grep -rn 'role="slider"' src/ -A2 | grep 'aria-label' | sort | uniq -d
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Failing Example
|
|
198
|
+
|
|
199
|
+
```html
|
|
200
|
+
<!-- BAD: custom slider using <div> with mouse events only — no keyboard, no ARIA -->
|
|
201
|
+
<div class="slider-track" onmousedown="startDrag(event)">
|
|
202
|
+
<div class="slider-thumb" style="left: 45%"></div>
|
|
203
|
+
</div>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Why it fails**: Not reachable by keyboard; no `role="slider"`; no `aria-valuenow`, `aria-valuemin`, or `aria-valuemax`; AT cannot read or change the value; touch users cannot interact via assistive touch.
|
|
207
|
+
**Grep detection**: `grep -rn '<div.*slider\|class="slider' src/ | grep -v 'role="slider"'`
|
|
208
|
+
**Fix**: Use native `<input type="range">` or add `role="slider"` + `aria-valuenow` + `aria-valuemin` + `aria-valuemax` + `aria-valuetext` + full keyboard event handlers (Arrow, Page, Home, End) to the thumb element.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Stepper / Wizard — Benchmark Spec
|
|
2
|
+
|
|
3
|
+
**Harvested from**: Carbon (ProgressIndicator + StepNavigation), Material 3 Stepper, Atlassian Design System, Mantine Stepper
|
|
4
|
+
**Wave**: 5 · **Category**: Advanced
|
|
5
|
+
**Spec file**: `reference/components/stepper.md`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
A Stepper (or Wizard) guides users through a sequential multi-step flow — onboarding, checkout, multi-page forms, settings setup. It communicates how many steps exist, which step is current, which are complete, and which are upcoming. Unlike Tabs (free navigation), a linear Stepper enforces order: the user must complete the current step before advancing. *(Carbon, Material 3, Atlassian, Mantine agree: step indicator list + content area + explicit Next/Back buttons is the canonical wizard pattern.)*
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Step indicator (role="list"):
|
|
19
|
+
● Step 1: Account ✓ Step 2: Profile ○ Step 3: Confirm
|
|
20
|
+
aria-current="step" completed upcoming
|
|
21
|
+
|
|
22
|
+
Content area:
|
|
23
|
+
[ Current step form/content ]
|
|
24
|
+
|
|
25
|
+
Actions:
|
|
26
|
+
[ Back ] [ Next ] / [ Submit ]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Part | Required | Notes |
|
|
30
|
+
|------|----------|-------|
|
|
31
|
+
| Step indicator list | Yes | `role="list"`; each step is `role="listitem"` |
|
|
32
|
+
| Step labels | Yes | Visible text per step; described state (e.g., "completed") |
|
|
33
|
+
| Current step marker | Yes | `aria-current="step"` on active step |
|
|
34
|
+
| Content area | Yes | Shows current step content (single panel or accordion) |
|
|
35
|
+
| Next button | Yes | Explicit label "Next" or "Continue"; validates current step first |
|
|
36
|
+
| Back button | Yes (if non-first step) | Returns to previous step; "Back" label |
|
|
37
|
+
| Submit button | Yes (final step) | "Submit" or context-specific label; replaces Next on last step |
|
|
38
|
+
| Step connector line | No | Visual line between step dots; decorative, `aria-hidden="true"` |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Variants
|
|
43
|
+
|
|
44
|
+
| Variant | Description | Systems |
|
|
45
|
+
|---------|-------------|---------|
|
|
46
|
+
| Linear / locked | Must complete steps in order; no jumping ahead | Carbon, Material 3, Atlassian, Mantine |
|
|
47
|
+
| Non-linear | Can jump to any previously completed step | Carbon (StepNavigation), Mantine (allowNextStepsSelect) |
|
|
48
|
+
| Horizontal | Step indicators in a row across the top | All systems (default) |
|
|
49
|
+
| Vertical | Step indicators stacked on the left | Carbon, Material 3 |
|
|
50
|
+
| Accordion stepper | All steps visible; current step expanded | Material 3 (docked), Mantine (vertical) |
|
|
51
|
+
| Simple (no icons) | Text + number only, no check icons | Atlassian (compact) |
|
|
52
|
+
|
|
53
|
+
**Norm** (≥3/4 systems agree): horizontal orientation is default; completed steps show a checkmark; current step is visually distinct; upcoming steps are muted.
|
|
54
|
+
**Diverge**: Material 3 uses filled circles with numbers; Carbon uses custom step icons; Mantine supports icons per step; Atlassian uses numbered circles.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## States
|
|
59
|
+
|
|
60
|
+
| State | Trigger | Visual | ARIA |
|
|
61
|
+
|-------|---------|--------|------|
|
|
62
|
+
| upcoming | Step not yet reached | Muted circle/number, secondary text color | — |
|
|
63
|
+
| current | Active step | Brand-color filled circle; bold label | `aria-current="step"` |
|
|
64
|
+
| completed | Step passed + valid | Check icon; full-opacity; clickable if non-linear | `aria-label="Step N: [name] - completed"` |
|
|
65
|
+
| error | Step has validation errors | Error color circle; error icon | `aria-label="Step N: [name] - has errors"` |
|
|
66
|
+
| disabled | Future step in linear flow | Muted; not clickable | Implicit (no click handler; cursor: default) |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Sizing & Spacing
|
|
71
|
+
|
|
72
|
+
| Size | Step Circle | Connector Height | Label Font | Gap Between Steps |
|
|
73
|
+
|------|-------------|-----------------|------------|-------------------|
|
|
74
|
+
| sm | 20px | 1px | 12px | 32px |
|
|
75
|
+
| md (default) | 32px | 2px | 14px | 48px |
|
|
76
|
+
| lg | 40px | 2px | 16px | 64px |
|
|
77
|
+
|
|
78
|
+
**Norm**: Step circles 32px default *(Carbon, Mantine)*. Horizontal spacing between steps should scale with the available width so the indicator spans the container. Connector line is centered between circles.
|
|
79
|
+
|
|
80
|
+
Cross-link: `reference/surfaces.md` — minimum 44×44px touch target for clickable step indicators.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Typography
|
|
85
|
+
|
|
86
|
+
- Step label: body-sm (completed/upcoming) → body-sm weight 600 (current)
|
|
87
|
+
- Step number/icon inside circle: caption-sm, center-aligned
|
|
88
|
+
- Step description (optional sub-label): caption-sm, secondary color
|
|
89
|
+
- Action buttons: body-md, weight 500 — same as standard button spec
|
|
90
|
+
|
|
91
|
+
Cross-link: `reference/typography.md` — label weight change for current state.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Keyboard & Accessibility
|
|
96
|
+
|
|
97
|
+
> **WAI-ARIA role**: `list` (step indicator container), `listitem` (each step), `button` (clickable completed steps in non-linear mode)
|
|
98
|
+
> **Required attributes**: `aria-current="step"` on active step; descriptive `aria-label` on completed steps (include state: "Step 2: Profile - completed"); `aria-label` on connector lines if not `aria-hidden`
|
|
99
|
+
|
|
100
|
+
### Keyboard Contract
|
|
101
|
+
|
|
102
|
+
*Derived from WAI-ARIA APG list and button patterns — https://www.w3.org/WAI/ARIA/apg/ — W3C — 2024*
|
|
103
|
+
|
|
104
|
+
| Key | Action |
|
|
105
|
+
|-----|--------|
|
|
106
|
+
| Tab | Move focus through interactive elements: clickable completed steps (non-linear), Back button, Next/Submit button |
|
|
107
|
+
| Enter / Space | Activate focused Back, Next, Submit button; activate clickable completed step (non-linear) |
|
|
108
|
+
| (No arrow-key navigation) | Steps are NOT tabs — do not implement roving tabindex / arrow-key navigation between steps |
|
|
109
|
+
|
|
110
|
+
Steps are NOT `role="tab"` and do not use the tab keyboard pattern. Upcoming steps are not focusable in linear mode. Only completed steps are interactive (and focusable) in non-linear mode.
|
|
111
|
+
|
|
112
|
+
### Accessibility Rules
|
|
113
|
+
|
|
114
|
+
- Step indicators MUST use `role="list"` + `role="listitem"` — not `role="tablist"` + `role="tab"` (tabs allow free navigation; wizard steps do not)
|
|
115
|
+
- Current step MUST have `aria-current="step"` — this is the correct token (not `aria-selected` or `aria-checked`)
|
|
116
|
+
- Completed steps in non-linear mode MUST be `<button>` elements (or have `role="button"` + `tabindex="0"`) with `aria-label` including the step name and "completed" state
|
|
117
|
+
- Step connector lines MUST be `aria-hidden="true"` — they are purely decorative
|
|
118
|
+
- Validate current step before allowing Next; display inline error messages with `aria-describedby` associations
|
|
119
|
+
- "Next", "Back", and "Submit" MUST be explicit text labels — do not use icon-only navigation buttons
|
|
120
|
+
- Announce step transitions via `aria-live="polite"` on the content region header (e.g., "Step 2 of 4: Profile")
|
|
121
|
+
|
|
122
|
+
Cross-link: `reference/accessibility.md` — aria-current values, list semantics.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Motion
|
|
127
|
+
|
|
128
|
+
| Transition | Duration | Easing | Notes |
|
|
129
|
+
|------------|----------|--------|-------|
|
|
130
|
+
| Step complete animation | 200ms | ease-out | Circle fill → checkmark draw |
|
|
131
|
+
| Content area transition | 250ms | ease-in-out | Fade or slide left/right between steps |
|
|
132
|
+
| Error state appear | 150ms | ease-out | Circle color change + icon fade in |
|
|
133
|
+
| Back navigation | 200ms | ease-in-out | Slide right (reverse direction) |
|
|
134
|
+
|
|
135
|
+
**BAN**: Do not use the same slide direction for both forward and backward step navigation — direction must be consistent with the mental model (forward = left, backward = right).
|
|
136
|
+
|
|
137
|
+
Cross-link: `reference/motion.md` — reduced-motion: skip slide; cross-fade content area only.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Do / Don't
|
|
142
|
+
|
|
143
|
+
### Do
|
|
144
|
+
- Use `role="list"` for the step indicator — steps are a list, not a tab set *(WAI-ARIA APG, Carbon)*
|
|
145
|
+
- Set `aria-current="step"` on the active step *(WAI-ARIA spec §aria-current)*
|
|
146
|
+
- Validate the current step before advancing and show inline errors *(Carbon, Atlassian)*
|
|
147
|
+
- Label Back/Next/Submit buttons explicitly — not icons or chevrons *(Material 3, Carbon, Mantine)*
|
|
148
|
+
|
|
149
|
+
### Don't
|
|
150
|
+
- Don't use `role="tablist"` for steps — tabs allow free navigation; wizard steps are ordered and gated *(diverges from all 4 systems)*
|
|
151
|
+
- Don't allow jumping to future unvisited steps in linear mode — breaks the sequential contract *(Carbon, Atlassian)*
|
|
152
|
+
- Don't use `aria-selected` on steps — `aria-current="step"` is the correct token *(WAI-ARIA spec)*
|
|
153
|
+
- Don't omit the Back button — users must be able to correct previous steps *(Material 3, Atlassian)*
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Anti-patterns Cross-links
|
|
158
|
+
|
|
159
|
+
| Anti-pattern | Entry |
|
|
160
|
+
|--------------|-------|
|
|
161
|
+
| BAN-14 | Stepper using role="tablist" — semantically incorrect navigation model — `reference/anti-patterns.md#ban-14` |
|
|
162
|
+
| BAN-07 | Missing aria-current on active step — `reference/anti-patterns.md#ban-07` |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Benchmark Citations
|
|
167
|
+
|
|
168
|
+
| Claim | Sources |
|
|
169
|
+
|-------|---------|
|
|
170
|
+
| Step indicator is role="list", not role="tablist" | WAI-ARIA APG, Carbon, Atlassian design docs |
|
|
171
|
+
| aria-current="step" on active step | WAI-ARIA spec §aria-current, Carbon a11y guide |
|
|
172
|
+
| Completed steps need aria-label with state | Carbon ProgressIndicator a11y docs, Atlassian |
|
|
173
|
+
| Validate before Next; show inline errors | Carbon, Atlassian wizard pattern guidelines |
|
|
174
|
+
| Explicit Next/Back/Submit text labels required | Material 3, Carbon, Mantine |
|
|
175
|
+
|
|
176
|
+
Full system URLs: `connections/design-corpora.md`
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Grep Signatures
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Step indicator using role="tablist" (incorrect — steps are not tabs)
|
|
184
|
+
grep -rn 'stepper\|wizard\|step-indicator\|StepIndicator' src/ | grep 'role="tablist"'
|
|
185
|
+
|
|
186
|
+
# Missing aria-current on active step
|
|
187
|
+
grep -rn 'stepper\|wizard\|\.step\b\|step--active\|step--current' src/ | grep -v 'aria-current'
|
|
188
|
+
|
|
189
|
+
# Step connector lines not aria-hidden (extraneous AT noise)
|
|
190
|
+
grep -rn 'step.*connector\|connector.*step\|\.step-line\|StepConnector' src/ | grep -v 'aria-hidden'
|
|
191
|
+
|
|
192
|
+
# Icon-only Next/Back buttons (no text label)
|
|
193
|
+
grep -rn 'wizard.*next\|stepper.*next\|wizard.*back\|stepper.*back' src/ | grep -v 'Next\|Back\|Continue\|Submit\|aria-label'
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Failing Example
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<!-- BAD: stepper using <ul role="tablist"> with <li role="tab"> — semantically wrong -->
|
|
202
|
+
<ul role="tablist" class="stepper">
|
|
203
|
+
<li role="tab" aria-selected="true" class="step step--active">
|
|
204
|
+
<span class="step-number">1</span>
|
|
205
|
+
<span class="step-label">Account</span>
|
|
206
|
+
</li>
|
|
207
|
+
<li role="tab" aria-selected="false" class="step step--upcoming">
|
|
208
|
+
<span class="step-number">2</span>
|
|
209
|
+
<span class="step-label">Profile</span>
|
|
210
|
+
</li>
|
|
211
|
+
<li role="tab" aria-selected="false" class="step step--upcoming">
|
|
212
|
+
<span class="step-number">3</span>
|
|
213
|
+
<span class="step-label">Confirm</span>
|
|
214
|
+
</li>
|
|
215
|
+
</ul>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Why it fails**: `role="tablist"` implies that all tabs are independently activatable and content switches freely — this is correct for Tabs but wrong for a wizard where steps are gated. AT users expect arrow-key navigation between tabs; in a wizard this would allow jumping to uncompleted future steps. `aria-selected` is the tab token; the correct token for a wizard is `aria-current="step"`.
|
|
219
|
+
**Grep detection**: `grep -rn 'role="tablist"' src/ | grep -i 'step\|wizard'`
|
|
220
|
+
**Fix**: Replace `<ul role="tablist">` with `<ol role="list">` (ordered list signals sequence), each `<li>` with `role="listitem"`, remove `aria-selected`, and add `aria-current="step"` to the current step. Make only completed steps keyboard-focusable (as `<button>`) in non-linear mode; upcoming steps are not interactive.
|