@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.
Files changed (58) hide show
  1. package/.claude-plugin/marketplace.json +12 -4
  2. package/.claude-plugin/plugin.json +22 -4
  3. package/CHANGELOG.md +111 -0
  4. package/README.md +27 -2
  5. package/agents/design-auditor.md +65 -1
  6. package/agents/design-context-builder.md +6 -1
  7. package/agents/design-doc-writer.md +21 -0
  8. package/agents/design-executor.md +22 -4
  9. package/agents/design-pattern-mapper.md +62 -0
  10. package/agents/design-phase-researcher.md +1 -1
  11. package/agents/motion-mapper.md +74 -9
  12. package/agents/token-mapper.md +8 -0
  13. package/package.json +16 -2
  14. package/reference/components/README.md +27 -23
  15. package/reference/components/alert.md +198 -0
  16. package/reference/components/badge.md +202 -0
  17. package/reference/components/breadcrumbs.md +198 -0
  18. package/reference/components/chip.md +209 -0
  19. package/reference/components/command-palette.md +228 -0
  20. package/reference/components/date-picker.md +227 -0
  21. package/reference/components/file-upload.md +219 -0
  22. package/reference/components/list.md +217 -0
  23. package/reference/components/menu.md +212 -0
  24. package/reference/components/navbar.md +211 -0
  25. package/reference/components/pagination.md +205 -0
  26. package/reference/components/progress.md +210 -0
  27. package/reference/components/rich-text-editor.md +226 -0
  28. package/reference/components/sidebar.md +211 -0
  29. package/reference/components/skeleton.md +197 -0
  30. package/reference/components/slider.md +208 -0
  31. package/reference/components/stepper.md +220 -0
  32. package/reference/components/table.md +229 -0
  33. package/reference/components/toast.md +200 -0
  34. package/reference/components/tree.md +225 -0
  35. package/reference/css-grid-layout.md +835 -0
  36. package/reference/data-visualization.md +333 -0
  37. package/reference/external/NOTICE.hyperframes +28 -0
  38. package/reference/form-patterns.md +245 -0
  39. package/reference/image-optimization.md +582 -0
  40. package/reference/information-architecture.md +255 -0
  41. package/reference/motion-advanced.md +754 -0
  42. package/reference/motion-easings.md +381 -0
  43. package/reference/motion-interpolate.md +282 -0
  44. package/reference/motion-spring.md +234 -0
  45. package/reference/motion-transition-taxonomy.md +155 -0
  46. package/reference/motion.md +20 -0
  47. package/reference/onboarding-progressive-disclosure.md +250 -0
  48. package/reference/output-contracts/motion-map.schema.json +135 -0
  49. package/reference/platforms.md +346 -0
  50. package/reference/registry.json +445 -220
  51. package/reference/registry.schema.json +4 -0
  52. package/reference/rtl-cjk-cultural.md +353 -0
  53. package/reference/user-research.md +360 -0
  54. package/reference/variable-fonts-loading.md +532 -0
  55. package/scripts/lib/easings.cjs +280 -0
  56. package/scripts/lib/parse-contract.cjs +220 -0
  57. package/scripts/lib/spring.cjs +160 -0
  58. package/scripts/tests/test-motion-provenance.sh +64 -0
@@ -0,0 +1,227 @@
1
+ # Date Picker — Benchmark Spec
2
+
3
+ **Harvested from**: Material 3, Carbon Design System, Atlassian Design System, Mantine DatePicker
4
+ **Wave**: 5 · **Category**: Advanced
5
+ **Spec file**: `reference/components/date-picker.md`
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ A Date Picker lets users select a single date or a date range through a calendar popover attached to a text input. It combines a formatted text field (for direct keyboard entry) with a visual month grid (for pointer or keyboard navigation). Use Date Picker when the date is human-meaningful and users may want to browse relative to a calendar context. Use a plain `<input type="date">` when native behavior is sufficient or a mobile-first audience is expected. *(Material 3, Carbon, Atlassian, Mantine agree: calendar popover + text input pairing is the canonical desktop pattern.)*
12
+
13
+ ---
14
+
15
+ ## Anatomy
16
+
17
+ ```
18
+ [ Input field "MM/DD/YYYY" ] [ Calendar icon button ]
19
+
20
+ └── Popover (role="dialog", aria-modal="true")
21
+ ├── Header: [ ← ] [ Month Year ] [ → ]
22
+ ├── Day-of-week row (aria-hidden="true")
23
+ └── Month grid (role="grid")
24
+ └── Week rows (role="row")
25
+ └── Day cells (role="gridcell")
26
+ └── Day button (role="button")
27
+
28
+ Range variant adds a second input field for end date.
29
+ ```
30
+
31
+ | Part | Required | Notes |
32
+ |------|----------|-------|
33
+ | Text input | Yes | Shows selected date; accepts direct keyboard entry |
34
+ | Format hint label | Yes | Visible "MM/DD/YYYY" supplement — NOT placeholder only |
35
+ | Calendar trigger button | Yes | Opens/closes popover; `aria-label="Open calendar"` |
36
+ | Popover (dialog) | Yes | `role="dialog"` + `aria-modal="true"` + focus trap |
37
+ | Month navigation | Yes | Previous/next month buttons; keyboard Page Up/Down |
38
+ | Month grid | Yes | `role="grid"` |
39
+ | Day cell | Yes | `role="gridcell"` containing `role="button"` for selectable days |
40
+ | Range highlight | No (range variant only) | Visual fill between start and end dates |
41
+
42
+ ---
43
+
44
+ ## Variants
45
+
46
+ | Variant | Description | Systems |
47
+ |---------|-------------|---------|
48
+ | Single date | One input + calendar popover | Material 3, Carbon, Atlassian, Mantine |
49
+ | Date range | Start + end input pair sharing one calendar | Carbon (DateRangePicker), Mantine (DateRangePicker), Atlassian |
50
+ | Date-time | Date selection + time selector panel | Material 3 (DateTimePicker), Mantine |
51
+ | Inline calendar | Calendar always visible, no popover | Mantine (Calendar), Material 3 (docked) |
52
+
53
+ **Norm** (≥3/4 systems agree): popover calendar attached to a text input is the dominant pattern; range uses two inputs sharing one calendar panel.
54
+ **Diverge**: Material 3 uses a modal dialog for mobile; Carbon and Mantine use an anchored popover for all viewports.
55
+
56
+ ---
57
+
58
+ ## States
59
+
60
+ | State | Trigger | Visual | ARIA |
61
+ |-------|---------|--------|------|
62
+ | default | — | Resting input + trigger icon | — |
63
+ | open | Trigger click or input focus + Enter | Popover visible, focus moves inside | `aria-expanded="true"` on trigger |
64
+ | day hover | Pointer over day | Background highlight | — |
65
+ | day focus | Keyboard navigation | Focus-visible ring on day button | — |
66
+ | day selected | Enter/Space or click | Filled background (brand color) | `aria-pressed="true"` on day button |
67
+ | range-in-progress | Start selected, end not yet | Partial fill from start | — |
68
+ | range-complete | Both start and end selected | Full highlight between dates | — |
69
+ | disabled date | Date excluded by min/max/filter | Muted; `tabindex="-1"` | `aria-disabled="true"` on day button |
70
+ | error | Invalid date typed | Red border + error message | `aria-invalid="true"` on input |
71
+
72
+ ---
73
+
74
+ ## Sizing & Spacing
75
+
76
+ | Size | Input Height | Popover Width | Day Cell Size | Font |
77
+ |------|-------------|---------------|---------------|------|
78
+ | sm | 32px | 256px | 32px | 13px |
79
+ | md (default) | 40px | 288px | 40px | 14px |
80
+ | lg | 48px | 320px | 44px | 16px |
81
+
82
+ **Norm**: 288px popover width fits a standard 7-column month grid with comfortable padding *(Carbon, Mantine)*.
83
+ Day cells must be ≥32px to meet minimum touch-target guidance; prefer 40px for mixed pointer/touch contexts.
84
+
85
+ Cross-link: `reference/surfaces.md` — minimum 44×44px accessible tap target via padding.
86
+
87
+ ---
88
+
89
+ ## Typography
90
+
91
+ - Input text: body-md weight 400; monospace or tabular-nums variant for date digits aids alignment
92
+ - Day numbers: body-sm, center-aligned within cell
93
+ - Month/year header: body-md weight 600
94
+ - Format hint ("MM/DD/YYYY"): caption-sm, secondary color — attached as visible `<label>` supplement or `<span aria-hidden="true">` paired with `aria-describedby` on the input
95
+
96
+ Cross-link: `reference/typography.md` — tabular-nums for date/time fields.
97
+
98
+ ---
99
+
100
+ ## Keyboard & Accessibility
101
+
102
+ > **WAI-ARIA role**: `dialog` (popover), `grid` (month table), `button` (day cells)
103
+ > **Required attributes**: `role="dialog"` + `aria-modal="true"` + `aria-label="Choose date"` on popover; `role="grid"` on month table; `role="gridcell"` + `role="button"` on each day
104
+
105
+ ### Keyboard Contract
106
+
107
+ *Adapted from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/ and grid pattern — W3C — 2024*
108
+
109
+ | Key | Action |
110
+ |-----|--------|
111
+ | Enter / Space | Open calendar when focus is on trigger; select focused day |
112
+ | Arrow Left / Right | Move focus to previous / next day |
113
+ | Arrow Up / Down | Move focus to same day in previous / next week |
114
+ | Page Up | Navigate to previous month |
115
+ | Page Down | Navigate to next month |
116
+ | Ctrl + Page Up | Navigate to previous year |
117
+ | Ctrl + Page Down | Navigate to next year |
118
+ | Home | Move focus to first day of current week |
119
+ | End | Move focus to last day of current week |
120
+ | Escape | Close calendar popover; return focus to trigger button |
121
+ | Tab | Move focus through interactive elements inside popover |
122
+
123
+ ### Accessibility Rules
124
+
125
+ - Calendar popover MUST use `role="dialog"` + `aria-modal="true"` — do not use a plain `<div>` overlay
126
+ - Focus MUST be trapped inside the open calendar; Escape closes and returns focus to the trigger
127
+ - Each selectable day MUST be a `<button>` (or element with `role="button"`) so keyboard and AT users can activate it
128
+ - Date format hint MUST be visible text, not only a placeholder (placeholder disappears on input and is not consistently announced)
129
+ - Disabled dates MUST use `aria-disabled="true"` and `tabindex="-1"` so they are skipped but still communicated to AT
130
+ - Range variant: announce selected range via `aria-label` on day buttons (e.g., `aria-label="April 15, 2025, start of range"`)
131
+ - Mobile: provide native `<input type="date">` fallback when touch device detected or user preference set
132
+
133
+ Cross-link: `reference/accessibility.md` — focus-trap pattern, dialog role requirements.
134
+
135
+ ---
136
+
137
+ ## Motion
138
+
139
+ | Transition | Duration | Easing | Notes |
140
+ |------------|----------|--------|-------|
141
+ | Popover open | 150ms | ease-out | Fade + subtle scale from trigger |
142
+ | Popover close | 100ms | ease-in | Fade out |
143
+ | Month change | 200ms | ease-in-out | Slide left/right or cross-fade |
144
+ | Day selection | 80ms | ease-out | Fill background color |
145
+ | Range fill | 150ms | ease-out | Animate fill between dates |
146
+
147
+ **BAN**: Do not animate the calendar grid with a full-page slide — disorienting for keyboard users navigating by month.
148
+
149
+ Cross-link: `reference/motion.md` — reduced-motion: omit slide, keep instant day selection.
150
+
151
+ ---
152
+
153
+ ## Do / Don't
154
+
155
+ ### Do
156
+ - Show the date format as visible text ("MM/DD/YYYY") near the input *(Carbon, Atlassian)*
157
+ - Ensure keyboard users can navigate the entire calendar without a pointer *(WAI-ARIA APG)*
158
+ - Allow direct text entry in the input field — some users know the date and do not need the calendar *(Mantine, Carbon)*
159
+ - Support locale-aware first day of week (Sunday vs. Monday) and locale month/day names *(Material 3, Mantine)*
160
+
161
+ ### Don't
162
+ - Don't use `<table>` with click-only cells — add `role="grid"` and full keyboard navigation *(WCAG 2.1 §2.1.1)*
163
+ - Don't use placeholder text alone to convey the date format — placeholder vanishes on input *(Carbon, Atlassian)*
164
+ - Don't trap focus permanently — Escape must always close the popover and restore focus *(WAI-ARIA APG)*
165
+ - Don't omit the native `<input type="date">` for mobile — custom calendars are unusable on small touch screens *(Material 3)*
166
+
167
+ ---
168
+
169
+ ## Anti-patterns Cross-links
170
+
171
+ | Anti-pattern | Entry |
172
+ |--------------|-------|
173
+ | BAN-07 | Placeholder as only label/hint — `reference/anti-patterns.md#ban-07` |
174
+ | BAN-12 | Custom overlay without focus trap — `reference/anti-patterns.md#ban-12` |
175
+
176
+ ---
177
+
178
+ ## Benchmark Citations
179
+
180
+ | Claim | Sources |
181
+ |-------|---------|
182
+ | Popover uses role="dialog" + aria-modal | WAI-ARIA APG dialog pattern, Carbon, Atlassian |
183
+ | Month grid uses role="grid" | WAI-ARIA APG grid pattern, Mantine docs |
184
+ | Arrow keys navigate days; Page Up/Down change month | WAI-ARIA APG, Carbon DatePicker keyboard docs |
185
+ | Format hint must be visible text, not only placeholder | Carbon, Atlassian design guidelines |
186
+ | Native input fallback for mobile | Material 3, Mantine (mobile prop) |
187
+
188
+ Full system URLs: `connections/design-corpora.md`
189
+
190
+ ---
191
+
192
+ ## Grep Signatures
193
+
194
+ ```bash
195
+ # Calendar popover missing role="dialog" (uses plain div overlay)
196
+ grep -rn 'calendar\|datepicker\|date-picker' src/ | grep -v 'role="dialog"'
197
+
198
+ # Day buttons missing keyboard handler (click-only calendar)
199
+ grep -rn 'role="gridcell"\|\.day\|\.calendar-day' src/ | grep -v 'onKeyDown\|on:keydown\|handleKey'
200
+
201
+ # Date input using placeholder for format hint only (no visible label supplement)
202
+ grep -rn '<input.*type="text".*date\|DateInput' src/ | grep 'placeholder.*MM\|placeholder.*dd' | grep -v 'aria-describedby\|<label\|hint'
203
+
204
+ # Missing aria-modal on calendar popover
205
+ grep -rn 'role="dialog"' src/ | grep -v 'aria-modal'
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Failing Example
211
+
212
+ ```html
213
+ <!-- BAD: calendar using <table> with click-only day cells, no keyboard navigation -->
214
+ <table class="calendar">
215
+ <tbody>
216
+ <tr>
217
+ <td class="day" onclick="selectDate('2025-04-01')">1</td>
218
+ <td class="day" onclick="selectDate('2025-04-02')">2</td>
219
+ <!-- ... -->
220
+ </tr>
221
+ </tbody>
222
+ </table>
223
+ ```
224
+
225
+ **Why it fails**: `<td>` cells are not focusable by default; no keyboard navigation; AT users cannot select dates; no `role="grid"` or `role="gridcell"`; click handler is the only interaction path.
226
+ **Grep detection**: `grep -rn '<table.*calendar\|<td.*onclick.*date\|<td.*selectDate' src/`
227
+ **Fix**: Replace with `<table role="grid">`, `<td role="gridcell">`, `<button>` inside each cell, and full Arrow/Page/Home/End keyboard handlers. Add `role="dialog"` + `aria-modal="true"` + focus trap on the popover wrapper.
@@ -0,0 +1,219 @@
1
+ # File Upload — Benchmark Spec
2
+
3
+ **Harvested from**: Polaris (DropZone), Carbon (FileUploader), Atlassian Design System, Material 3
4
+ **Wave**: 5 · **Category**: Advanced
5
+ **Spec file**: `reference/components/file-upload.md`
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ A File Upload component lets users attach one or more files by dragging them onto a drop zone or clicking to open the native file picker. It must work for all users: keyboard-only users activate the hidden-but-accessible `<input type="file">`, while pointer users can drag-drop. A file list tracks upload progress, status, and provides remove actions. *(Polaris, Carbon, Atlassian agree: drop zone + accessible file input + per-file status list is the canonical pattern.)*
12
+
13
+ ---
14
+
15
+ ## Anatomy
16
+
17
+ ```
18
+ ┌─────────────────────────────────┐
19
+ │ Drop zone │
20
+ │ [ Cloud icon ] │
21
+ │ "Drag files here or" │
22
+ │ [ Browse files ] ← triggers │
23
+ │ <input type="file"> │
24
+ └─────────────────────────────────┘
25
+
26
+ File list (appears after selection):
27
+ ┌────────────────────────────────────────────────┐
28
+ │ 📄 report.pdf 245 KB [======== ] 80% [✕] │
29
+ │ 📄 photo.jpg 1.2 MB ✓ Done [✕] │
30
+ │ 📄 data.csv 88 KB ✗ Error [✕] │
31
+ └────────────────────────────────────────────────┘
32
+ ```
33
+
34
+ | Part | Required | Notes |
35
+ |------|----------|-------|
36
+ | Drop zone container | Yes | Dashed border; drag-over changes fill + border color |
37
+ | `<input type="file">` | Yes | MUST be accessible (not `display:none`) — keyboard fallback |
38
+ | Browse trigger button | Yes | Visually activates the file input; must be a `<button>` or `<label>` |
39
+ | File list | Yes (when files selected) | Per-file name, size, status, progress bar, remove button |
40
+ | Progress bar | Yes (during upload) | `role="progressbar"` + `aria-valuenow` per file or overall |
41
+ | Remove button | Yes | `aria-label="Remove [filename]"` |
42
+ | Error region | Yes (on error) | `aria-live="assertive"` for upload errors |
43
+
44
+ ---
45
+
46
+ ## Variants
47
+
48
+ | Variant | Description | Systems |
49
+ |---------|-------------|---------|
50
+ | Drop zone | Large dashed-border target area with drag-and-drop | Polaris, Carbon, Atlassian, Material 3 |
51
+ | Compact / inline | Small "Attach file" button only; no large drop area | Carbon (FileUploaderItem), Atlassian |
52
+ | Avatar/image uploader | Circular or rectangular crop zone for single image | Material 3, Polaris |
53
+ | Multi-file | `multiple` attr; list of uploaded files | All systems |
54
+ | Single-file | No `multiple`; replaces previous selection | Carbon, Polaris |
55
+
56
+ **Norm** (≥3/4 systems agree): drop zone + browse button + file list is the standard desktop pattern; progress bar per file during upload.
57
+ **Diverge**: Polaris auto-starts upload on drop; Carbon shows "Add files" button after initial selection to allow adding more; Material 3 defers to app logic.
58
+
59
+ ---
60
+
61
+ ## States
62
+
63
+ | State | Trigger | Visual | ARIA |
64
+ |-------|---------|--------|------|
65
+ | default | — | Dashed border, instructional text | — |
66
+ | drag-over | File dragged over zone | Filled background + solid border color change | `aria-dropeffect="copy"` (deprecated but still useful) |
67
+ | drag-invalid | Wrong file type dragged over | Error border color; tooltip/message | — |
68
+ | uploading | File being sent | Per-file progress bar animating | `aria-valuenow` on progressbar |
69
+ | upload-done | Transfer complete | Check icon; status text "Done" | — |
70
+ | upload-error | Transfer failed | Error icon; error message per file | `aria-live="assertive"` error region |
71
+ | disabled | `disabled` prop | 38% opacity; drag events ignored | `aria-disabled="true"` on zone |
72
+
73
+ ---
74
+
75
+ ## Sizing & Spacing
76
+
77
+ | Size | Drop Zone Min Height | Border Radius | Font |
78
+ |------|---------------------|---------------|------|
79
+ | sm | 80px | 4px | 13px |
80
+ | md (default) | 128px | 8px | 14px |
81
+ | lg | 200px | 12px | 16px |
82
+
83
+ **Norm**: Drop zone should be large enough that it is comfortably hittable — 128px minimum height for default. File list rows are 48–56px tall for accessible remove-button target size *(Carbon, Polaris)*.
84
+
85
+ Cross-link: `reference/surfaces.md` — minimum 44×44px touch targets for remove buttons.
86
+
87
+ ---
88
+
89
+ ## Typography
90
+
91
+ - Drop zone instruction: body-md, centered, secondary color
92
+ - "Browse files" link/button: body-md, primary link or button style
93
+ - File name in list: body-sm, truncated with ellipsis (max-width on container), full name in `title` attribute
94
+ - File size: caption-sm, secondary color
95
+ - Status text (Done / Error): caption-sm, success or error semantic color
96
+
97
+ Cross-link: `reference/typography.md` — truncation rules.
98
+
99
+ ---
100
+
101
+ ## Keyboard & Accessibility
102
+
103
+ > **WAI-ARIA role**: `button` (browse trigger); `progressbar` (upload progress); `status` or `log` (file list updates)
104
+ > **Required attributes**: `aria-label="Remove [filename]"` on remove buttons; `aria-valuenow` + `aria-valuemin` + `aria-valuemax` on progressbar; `aria-live="assertive"` on error region
105
+
106
+ ### Keyboard Contract
107
+
108
+ *Derived from native `<input type="file">` behavior and WAI-ARIA APG button pattern — W3C — 2024*
109
+
110
+ | Key | Action |
111
+ |-----|--------|
112
+ | Tab | Move focus to browse button / file input |
113
+ | Enter / Space | Activate browse button — opens native file picker dialog |
114
+ | Tab (in file list) | Move through file items and remove buttons |
115
+ | Enter / Space (on remove button) | Remove file from list |
116
+
117
+ Drag-and-drop is pointer-only; keyboard users MUST be able to complete the entire task via the file input alone.
118
+
119
+ ### Accessibility Rules
120
+
121
+ - `<input type="file">` MUST NOT use `display:none` or `visibility:hidden` — use `opacity:0` positioned absolutely with dimensions matching the trigger, OR keep a visible file input alongside the drop zone
122
+ - The browse trigger MUST be a `<button>` or `<label for="file-input">` so it is keyboard-focusable and activates the input
123
+ - Remove buttons MUST have `aria-label="Remove [filename]"` — an icon-only ✕ with no accessible name fails AT users
124
+ - Upload errors MUST be announced via `aria-live="assertive"` — do not rely solely on visual indicators
125
+ - Progress bars MUST keep `aria-valuenow` updated throughout upload
126
+ - `accept` attribute MUST match the visible allowed-types hint text so users are not surprised by rejection
127
+ - File list additions/removals should be announced via `aria-live="polite"` on the list container
128
+
129
+ Cross-link: `reference/accessibility.md` — aria-live regions, accessible file input patterns.
130
+
131
+ ---
132
+
133
+ ## Motion
134
+
135
+ | Transition | Duration | Easing | Notes |
136
+ |------------|----------|--------|-------|
137
+ | Drop zone drag-over | 100ms | ease-out | Background fill + border color |
138
+ | File list item enter | 200ms | ease-out | Slide-in from top or fade-in |
139
+ | File list item remove | 150ms | ease-in | Fade + collapse height |
140
+ | Progress bar fill | continuous | linear | Matches upload byte progress |
141
+ | Upload complete tick | 200ms | ease-out | Check icon draw animation |
142
+
143
+ **BAN**: Do not animate progress bar with CSS only at a fixed pace — progress MUST reflect actual upload percentage via `aria-valuenow`.
144
+
145
+ Cross-link: `reference/motion.md` — reduced-motion: skip slide/collapse animations; keep progress bar updates.
146
+
147
+ ---
148
+
149
+ ## Do / Don't
150
+
151
+ ### Do
152
+ - Keep `<input type="file">` accessible at all times (opacity:0 trick or visible) *(WAI-ARIA, Polaris, Carbon)*
153
+ - Provide `aria-label="Remove [filename]"` on every remove button *(Carbon, Atlassian)*
154
+ - Show file name, size, and status in the file list *(Polaris, Carbon, Atlassian)*
155
+ - Announce upload errors via `aria-live="assertive"` *(WCAG 2.1 §4.1.3 Status Messages)*
156
+
157
+ ### Don't
158
+ - Don't use `display:none` on the file input — keyboard and AT users cannot trigger the picker *(WCAG 2.1 §2.1.1)*
159
+ - Don't omit the `accept` hint text — users should know allowed types before selecting *(Polaris, Carbon)*
160
+ - Don't show only drag-drop UI with no browse button — drag is inaccessible to keyboard users *(Carbon, Atlassian)*
161
+ - Don't use a generic `aria-label="Remove"` on remove buttons — AT users cannot identify which file *(Carbon)*
162
+
163
+ ---
164
+
165
+ ## Anti-patterns Cross-links
166
+
167
+ | Anti-pattern | Entry |
168
+ |--------------|-------|
169
+ | BAN-08 | File input hidden with display:none — keyboard inaccessible — `reference/anti-patterns.md#ban-08` |
170
+ | BAN-13 | Icon-only action button without aria-label — `reference/anti-patterns.md#ban-13` |
171
+
172
+ ---
173
+
174
+ ## Benchmark Citations
175
+
176
+ | Claim | Sources |
177
+ |-------|---------|
178
+ | input type="file" must not be display:none | WCAG 2.1 §2.1.1, Carbon, Polaris accessibility guides |
179
+ | Remove button needs aria-label="Remove [filename]" | Carbon FileUploader, Atlassian, Polaris |
180
+ | Upload errors need aria-live="assertive" | WCAG 2.1 §4.1.3, Material 3 |
181
+ | Progress bar needs aria-valuenow updates | WAI-ARIA progressbar role spec |
182
+ | Drag-over state: background fill + border change | Polaris, Carbon, Atlassian drop zone specs |
183
+
184
+ Full system URLs: `connections/design-corpora.md`
185
+
186
+ ---
187
+
188
+ ## Grep Signatures
189
+
190
+ ```bash
191
+ # File input hidden with display:none (keyboard inaccessible)
192
+ grep -rn 'type="file"' src/ | grep -v 'opacity\|position.*absolute' | grep 'display.*none\|visibility.*hidden'
193
+
194
+ # Remove button missing aria-label (icon-only, no accessible name)
195
+ grep -rn 'remove.*button\|btn.*remove\|✕\|×' src/ | grep -v 'aria-label'
196
+
197
+ # Progress bar missing aria-valuenow
198
+ grep -rn 'role="progressbar"' src/ | grep -v 'aria-valuenow'
199
+
200
+ # Upload error region without aria-live
201
+ grep -rn 'upload.*error\|file.*error\|error.*upload' src/ | grep -v 'aria-live'
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Failing Example
207
+
208
+ ```html
209
+ <!-- BAD: drop zone with display:none on the actual <input> — keyboard and AT users can't trigger file picker -->
210
+ <div class="drop-zone" ondrop="handleDrop(event)" ondragover="handleDragOver(event)">
211
+ <p>Drag files here</p>
212
+ <input type="file" id="file-input" style="display:none" onchange="handleFiles(event)">
213
+ <button onclick="document.getElementById('file-input').click()">Browse</button>
214
+ </div>
215
+ ```
216
+
217
+ **Why it fails**: `display:none` removes the input from accessibility tree and tab order. The JavaScript `.click()` workaround does not work reliably with all AT. Keyboard users pressing Enter/Space on "Browse" may get inconsistent behavior across browsers. Screen reader users cannot discover or activate the file input directly.
218
+ **Grep detection**: `grep -rn 'type="file".*display.*none\|display.*none.*type="file"' src/`
219
+ **Fix**: Use `opacity:0; position:absolute; width:100%; height:100%` on the input (matching the browse button dimensions), or place a visible `<input type="file">` and style the button as a `<label for="file-input">` so clicking the label activates the input natively without JavaScript.
@@ -0,0 +1,217 @@
1
+ # List (Interactive & Display) — Benchmark Spec
2
+
3
+ **Harvested from**: Carbon, Polaris, Material 3, Mantine, WAI-ARIA APG, UUPM (app-interface, MIT)
4
+ **Wave**: 4 · **Category**: Navigation & Data
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ A list component handles two distinct patterns: (1) a display list renders a series of items using semantic `<ul>/<ol>/<li>` HTML — no ARIA needed; (2) an interactive list (listbox) presents selectable options with keyboard navigation and selection state. Use a display list for content; use an interactive listbox when users choose one or more items from a set. *(Carbon, Polaris, Material 3 all define separate display and interactive list patterns)*
11
+
12
+ ---
13
+
14
+ ## Anatomy
15
+
16
+ ```
17
+ Display list: Interactive listbox:
18
+ <ul> <div role="listbox"
19
+ <li>Item one</li> aria-multiselectable="false"
20
+ <li>Item two</li> aria-label="Assignees">
21
+ <li>Item three</li> <div role="option"
22
+ </ul> aria-selected="true">Alice</div>
23
+ <div role="option"
24
+ aria-selected="false">Bob</div>
25
+ </div>
26
+ ```
27
+
28
+ | Part | Required | Notes |
29
+ |------|----------|-------|
30
+ | List container | Yes | `<ul>` / `<ol>` (display) or `role="listbox"` (interactive) |
31
+ | List item | Yes | `<li>` (display) or `role="option"` (interactive) |
32
+ | `aria-selected` | Interactive only | `true`/`false` on each `role="option"` |
33
+ | `aria-multiselectable` | Interactive only | `true` if multi-select; default `false` |
34
+ | `aria-label` / `aria-labelledby` | Interactive only | Describes the listbox purpose |
35
+ | Empty state | No | Min 200px height; illustration + message + optional CTA |
36
+ | Virtual scroll | Conditional | At > 100 items; TanStack Virtual or react-virtual |
37
+
38
+ ---
39
+
40
+ ## Variants
41
+
42
+ | Variant | Description | Systems |
43
+ |---------|-------------|---------|
44
+ | Unordered display | `<ul>` bullet list; purely semantic | All systems |
45
+ | Ordered display | `<ol>` numbered list; sequential content | All systems |
46
+ | Single-select listbox | One item selectable at a time | Carbon, Polaris, Material 3 |
47
+ | Multi-select listbox | Multiple items selectable (Shift+Click, Ctrl+Click) | Carbon, Material 3, Mantine |
48
+ | List / detail panel | Left-panel list + right detail pane | UUPM app-interface (MIT) |
49
+ | Recent-items list | Time-ordered recent items with timestamps | UUPM app-interface (MIT) |
50
+ | Virtualized | Windowed rendering for large datasets (> 100 items) | Carbon, Mantine |
51
+
52
+ **Norm** (≥4 systems agree): `role="listbox"` + `role="option"` for interactive; native `<ul>/<li>` for display.
53
+ **Diverge**: Material 3 calls the interactive variant "ListItem with selectable state"; Carbon uses "ContentSwitcher" for small sets and "MultiSelect" for large. Pattern semantics are identical.
54
+
55
+ ---
56
+
57
+ ## States
58
+
59
+ | State | Trigger | Visual | ARIA |
60
+ |-------|---------|--------|------|
61
+ | default | — | Items visible; none selected | `aria-selected="false"` on all options |
62
+ | option-hover | pointer over | 8% overlay | — |
63
+ | option-focus | keyboard focus | 2px focus-visible ring | managed via `tabindex` |
64
+ | option-selected | click or Enter/Space | Filled highlight; checkmark for multi-select | `aria-selected="true"` |
65
+ | option-disabled | disabled prop | 38% opacity; cursor: default | `aria-disabled="true"` |
66
+ | empty | no items | Empty state (illustration + text + CTA) | `aria-label` on empty container |
67
+ | loading | data fetch | Skeleton items | `aria-busy="true"` on listbox container |
68
+
69
+ ---
70
+
71
+ ## Sizing & Spacing
72
+
73
+ | Element | Value | Notes |
74
+ |---------|-------|-------|
75
+ | Item height | 40px (default) | 32px compact; 48px comfortable |
76
+ | Item padding H | 12–16px | Icon + 8px gap if icon present |
77
+ | Empty state min-height | 200px | Prevents visually collapsed empty container |
78
+ | Virtual viewport | ~400–600px | Clip height for virtualised scroll |
79
+ | List max-height | 400px default | Scroll within list container |
80
+
81
+ **Norm**: 40px item height (Carbon, Polaris, Mantine). Max-height + internal scroll for contained lists.
82
+
83
+ ---
84
+
85
+ ## Typography
86
+
87
+ - Display list: inherits parent body text; `<li>` marker via `list-style-type`
88
+ - Interactive option label: body-sm (13–14px), weight 400; selected weight 500
89
+ - Secondary text / metadata: label-xs (11–12px), `color: --text-subtle`
90
+ - Empty state heading: heading-sm, center-aligned
91
+ - Empty state body: body-sm, `color: --text-subtle`
92
+
93
+ Cross-link: `reference/typography.md` — body-sm, heading scale
94
+
95
+ ---
96
+
97
+ ## Keyboard & Accessibility
98
+
99
+ > **WAI-ARIA role**: `listbox` (interactive container), `option` (each item)
100
+ > **Required attributes**: `aria-selected` on each `role="option"`; `aria-label` or `aria-labelledby` on `role="listbox"`; `aria-multiselectable="true"` for multi-select
101
+
102
+ ### Keyboard Contract
103
+
104
+ *Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/listbox/ — W3C — 2024*
105
+
106
+ | Key | Action |
107
+ |-----|--------|
108
+ | ArrowDown | Moves focus to next option (wraps to first) |
109
+ | ArrowUp | Moves focus to previous option (wraps to last) |
110
+ | Home | Moves focus to first option |
111
+ | End | Moves focus to last option |
112
+ | Enter / Space | Selects the focused option (single-select) |
113
+ | Shift+ArrowDown | Extends selection downward (multi-select) |
114
+ | Shift+ArrowUp | Extends selection upward (multi-select) |
115
+ | Ctrl+A | Selects all options (multi-select) |
116
+ | A–Z | Moves focus to next option starting with typed character |
117
+
118
+ ### Accessibility Rules
119
+
120
+ - Display lists use native `<ul>/<li>` — no ARIA roles needed; they are already accessible
121
+ - Interactive lists MUST use `role="listbox"` + `role="option"` — not `<ul>/<li>` with click handlers
122
+ - `aria-selected` MUST be present on every `role="option"` (either `true` or `false`)
123
+ - Multi-select listbox MUST declare `aria-multiselectable="true"` on the container
124
+ - Virtual scroll: all options in the virtualised window must have correct `aria-posinset` and `aria-setsize` attributes
125
+ - Empty state container MUST have `aria-label` or `aria-live` so AT announces the empty state
126
+
127
+ Cross-link: `reference/accessibility.md` — listbox pattern, virtual list accessibility
128
+
129
+ ---
130
+
131
+ ## Motion
132
+
133
+ | Transition | Duration | Easing | Notes |
134
+ |------------|----------|--------|-------|
135
+ | Option selection highlight | 100ms | ease-out | Background color only |
136
+ | Skeleton item shimmer | 1500ms | linear loop | Loading placeholder |
137
+ | Empty state entry | 150ms | ease-out | Fade in |
138
+
139
+ **BAN**: Do not animate item reordering unless using a deliberate drag-and-drop library — unsolicited reordering causes disorientation.
140
+
141
+ Cross-link: `reference/motion.md` — skeleton shimmer, list animations
142
+
143
+ ---
144
+
145
+ ## Do / Don't
146
+
147
+ ### Do
148
+ - Use native `<ul>/<ol>/<li>` for display-only lists — no ARIA needed *(WAI-ARIA APG)*
149
+ - Use `role="listbox"` + `role="option"` for selectable lists *(WAI-ARIA APG, Carbon, Polaris)*
150
+ - Virtualise at > 100 items to prevent DOM bloat *(Carbon, Mantine)*
151
+ - Provide a meaningful empty state with a CTA when the list can be populated *(Polaris, Material 3)*
152
+
153
+ ### Don't
154
+ - Don't use `<div onClick>` list items without `role="option"` — keyboard-inaccessible *(WCAG 2.1.1)*
155
+ - Don't omit `aria-selected` on options — AT cannot determine what is selected *(WAI-ARIA APG)*
156
+ - Don't use `<ul>/<li>` with `role="option"` — mixing native list semantics and listbox ARIA creates conflicts *(WAI-ARIA)*
157
+ - Don't load all items at once when count > 100 — renders slowly and wastes memory *(Carbon, Mantine)*
158
+
159
+ ---
160
+
161
+ ## Anti-patterns Cross-links
162
+
163
+ | Anti-pattern | Entry |
164
+ |--------------|-------|
165
+ | BAN-04 | `transition: all` on interactive elements — `reference/anti-patterns.md#ban-04` |
166
+
167
+ ---
168
+
169
+ ## Benchmark Citations
170
+
171
+ | Claim | Sources |
172
+ |-------|---------|
173
+ | role="listbox" + role="option" for interactive lists | WAI-ARIA APG listbox pattern |
174
+ | aria-selected required on every option | WAI-ARIA APG, Carbon, Polaris |
175
+ | aria-multiselectable="true" for multi-select | WAI-ARIA APG |
176
+ | Virtualise at > 100 items | Carbon, Mantine (TanStack Virtual) |
177
+ | 200px min empty state height | Carbon, Polaris HIG |
178
+
179
+ Full system URLs: `connections/design-corpora.md`
180
+
181
+ ---
182
+
183
+ ## Grep Signatures
184
+
185
+ ```bash
186
+ # Interactive list items using <div> without role="option"
187
+ grep -rn '<div' src/ | grep -i 'list.*item\|list-item\|listitem' | grep 'onClick\|on:click' | grep -v 'role='
188
+
189
+ # Missing aria-selected on option elements
190
+ grep -rn 'role="option"' src/ | grep -v 'aria-selected'
191
+
192
+ # Listbox missing aria-label
193
+ grep -rn 'role="listbox"' src/ | grep -v 'aria-label\|aria-labelledby'
194
+
195
+ # <ul>/<li> used with role="option" (semantics conflict)
196
+ grep -rn 'role="option"' src/ | grep '<li'
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Failing Example
202
+
203
+ ```html
204
+ <!-- BAD: interactive list items using <div onClick> with no keyboard support -->
205
+ <div class="user-list"> <!-- no role="listbox" -->
206
+ <div class="list-item selected" onclick="selectUser('alice')">
207
+ Alice Chen
208
+ </div>
209
+ <div class="list-item" onclick="selectUser('bob')">
210
+ Bob Tanaka
211
+ </div>
212
+ </div>
213
+ ```
214
+
215
+ **Why it fails**: No `role="listbox"` on container; no `role="option"` on items; no `aria-selected`; items not keyboard-focusable (no `tabindex`); arrow-key navigation does nothing; screen readers see two unlabelled `<div>` elements.
216
+ **Grep detection**: `grep -rn '<div.*onClick\|<div.*on:click' src/ | grep -i 'list.*item\|listitem'`
217
+ **Fix**: Use `<div role="listbox" aria-label="Users">` with `<div role="option" tabindex="-1" aria-selected="false">` items; implement roving `tabindex` and arrow-key handlers.