@hegemonart/get-design-done 1.15.0 → 1.18.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 (70) hide show
  1. package/.claude-plugin/marketplace.json +9 -5
  2. package/.claude-plugin/plugin.json +19 -5
  3. package/CHANGELOG.md +122 -0
  4. package/README.md +41 -0
  5. package/SKILL.md +4 -1
  6. package/agents/component-benchmark-harvester.md +112 -0
  7. package/agents/component-benchmark-synthesizer.md +88 -0
  8. package/agents/design-auditor.md +60 -1
  9. package/agents/design-doc-writer.md +21 -0
  10. package/agents/design-executor.md +22 -4
  11. package/agents/design-pattern-mapper.md +61 -0
  12. package/agents/motion-mapper.md +74 -9
  13. package/agents/token-mapper.md +8 -0
  14. package/connections/design-corpora.md +158 -0
  15. package/package.json +13 -3
  16. package/reference/components/README.md +94 -0
  17. package/reference/components/TEMPLATE.md +184 -0
  18. package/reference/components/accordion.md +217 -0
  19. package/reference/components/alert.md +198 -0
  20. package/reference/components/badge.md +202 -0
  21. package/reference/components/breadcrumbs.md +198 -0
  22. package/reference/components/button.md +195 -0
  23. package/reference/components/card.md +200 -0
  24. package/reference/components/checkbox.md +207 -0
  25. package/reference/components/chip.md +209 -0
  26. package/reference/components/command-palette.md +228 -0
  27. package/reference/components/date-picker.md +227 -0
  28. package/reference/components/drawer.md +201 -0
  29. package/reference/components/file-upload.md +219 -0
  30. package/reference/components/input.md +208 -0
  31. package/reference/components/label.md +200 -0
  32. package/reference/components/link.md +193 -0
  33. package/reference/components/list.md +217 -0
  34. package/reference/components/menu.md +212 -0
  35. package/reference/components/modal-dialog.md +210 -0
  36. package/reference/components/navbar.md +211 -0
  37. package/reference/components/pagination.md +205 -0
  38. package/reference/components/popover.md +197 -0
  39. package/reference/components/progress.md +210 -0
  40. package/reference/components/radio.md +203 -0
  41. package/reference/components/rich-text-editor.md +226 -0
  42. package/reference/components/select-combobox.md +219 -0
  43. package/reference/components/sidebar.md +211 -0
  44. package/reference/components/skeleton.md +197 -0
  45. package/reference/components/slider.md +208 -0
  46. package/reference/components/stepper.md +220 -0
  47. package/reference/components/switch.md +194 -0
  48. package/reference/components/table.md +229 -0
  49. package/reference/components/tabs.md +213 -0
  50. package/reference/components/toast.md +200 -0
  51. package/reference/components/tooltip.md +201 -0
  52. package/reference/components/tree.md +225 -0
  53. package/reference/css-grid-layout.md +835 -0
  54. package/reference/external/NOTICE.hyperframes +28 -0
  55. package/reference/image-optimization.md +582 -0
  56. package/reference/motion-advanced.md +754 -0
  57. package/reference/motion-easings.md +381 -0
  58. package/reference/motion-interpolate.md +282 -0
  59. package/reference/motion-spring.md +234 -0
  60. package/reference/motion-transition-taxonomy.md +155 -0
  61. package/reference/motion.md +20 -0
  62. package/reference/output-contracts/motion-map.schema.json +135 -0
  63. package/reference/registry.json +285 -0
  64. package/reference/registry.schema.json +6 -1
  65. package/reference/variable-fonts-loading.md +532 -0
  66. package/scripts/lib/easings.cjs +280 -0
  67. package/scripts/lib/parse-contract.cjs +220 -0
  68. package/scripts/lib/spring.cjs +160 -0
  69. package/scripts/tests/test-motion-provenance.sh +64 -0
  70. package/skills/benchmark/SKILL.md +105 -0
@@ -0,0 +1,200 @@
1
+ # Toast / Snackbar — Benchmark Spec
2
+
3
+ **Harvested from**: Radix UI Toast, Material 3 (Snackbar), Polaris (Toast), Carbon (Notification Toast)
4
+ **Wave**: 3 · **Category**: Feedback
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ A toast (snackbar) is a transient, non-blocking notification that appears briefly to confirm a completed action or communicate a system status. It auto-dismisses after 4–8 seconds (configurable) and does not require user acknowledgement for info/success variants. Use toast for low-urgency, time-sensitive feedback (save-confirmation, settings-saved). For persistent in-page messaging that demands attention, use Alert. *(Radix, Material 3, Polaris, Carbon agree: toast = ephemeral, non-blocking)*
11
+
12
+ ---
13
+
14
+ ## Anatomy
15
+
16
+ ```
17
+ ┌──────────────────────────────────────────────┐
18
+ │ [icon?] Message text [Action?] [✕?] │
19
+ └──────────────────────────────────────────────┘
20
+ ↑ role="status" or role="alert"
21
+ ↑ positioned: bottom-right (default)
22
+ ```
23
+
24
+ | Part | Required | Notes |
25
+ |------|----------|-------|
26
+ | Container | Yes | Positioned overlay; `role="status"` or `role="alert"` |
27
+ | Message text | Yes | Concise (≤80 chars); describes what happened |
28
+ | Severity icon | No | Reinforces variant; never sole differentiator |
29
+ | Action button | No | Single CTA, max 2 words (e.g. "Undo", "View") |
30
+ | Dismiss button | No | Required when auto-dismiss is disabled or severity is error |
31
+ | Progress indicator | No | Optional strip showing remaining display time |
32
+
33
+ ---
34
+
35
+ ## Variants
36
+
37
+ | Variant | Description | Systems |
38
+ |---------|-------------|---------|
39
+ | Info | Neutral status update; `role="status"` polite | Radix, Material 3, Polaris, Carbon |
40
+ | Success | Confirmed completion; `role="status"` polite | All |
41
+ | Warning | Action may have side effects; `role="alert"` assertive | Material 3, Carbon, Polaris |
42
+ | Error | Failure requiring attention; `role="alert"` assertive; persistent | All |
43
+
44
+ **Norm** (≥4/18 systems agree): info/success use polite live region; warning/error use assertive; error variant is persistent (no auto-dismiss) or has explicit dismiss.
45
+ **Diverge**: Material 3 calls this "Snackbar" (single action, no icon); Carbon calls it "Toast notification" (icon required). Polaris uses icon + title for structured variant. All converge on the transient + positioned pattern.
46
+
47
+ ---
48
+
49
+ ## States
50
+
51
+ | State | Trigger | Visual | ARIA |
52
+ |-------|---------|--------|------|
53
+ | entering | mount | Slide-in from bottom-right + fade-in, 200ms ease-out | Live region populated |
54
+ | visible | auto | Full opacity, static position | `role="status"` or `role="alert"` |
55
+ | hover / focus | pointer/keyboard | Auto-dismiss timer paused | — |
56
+ | exiting | dismiss / timeout | Slide-out + fade-out, 150ms ease-in | Removed from DOM |
57
+ | queued | >3 toasts active | Off-screen; waits for visible slot | Not yet in DOM |
58
+
59
+ ---
60
+
61
+ ## Sizing & Spacing
62
+
63
+ | Property | Value | Notes |
64
+ |----------|-------|-------|
65
+ | Min width | 288px | Prevents squished single-word toasts |
66
+ | Max width | 400px | *(Material 3: 344px, Carbon: 480px)* |
67
+ | Padding | 12px 16px | |
68
+ | Gap between stacked | 8px | Toasts stack vertically; max 3 visible |
69
+ | Border radius | 8px | *(Material 3: 4px, Polaris: 8px, Carbon: 0px)* |
70
+ | Position (default) | bottom-right | 16–24px from viewport edge |
71
+
72
+ **Norm**: Stack vertically with 8px gap; max 3 visible, queue the rest *(Radix, Polaris, Carbon)*.
73
+
74
+ ---
75
+
76
+ ## Typography
77
+
78
+ - Message: body-sm (14px/400) — same as body to ensure readability at a glance
79
+ - Action label: label-sm (13px/500) — slightly heavier to signal interactivity
80
+ - No wrapping beyond 2 lines — if message exceeds 2 lines, use Alert instead
81
+
82
+ Cross-link: `reference/typography.md` — body-sm, label-sm definitions
83
+
84
+ ---
85
+
86
+ ## Keyboard & Accessibility
87
+
88
+ > **WAI-ARIA role**: `status` (info/success) or `alert` (warning/error)
89
+ > **Required attributes**: `role` on container; `aria-label` on dismiss button; `aria-live` region must be pre-mounted in DOM
90
+
91
+ ### Keyboard Contract
92
+
93
+ *Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/alert/ — W3C — 2024*
94
+
95
+ | Key | Action |
96
+ |-----|--------|
97
+ | Tab | Moves focus to action button or dismiss button if present |
98
+ | Enter / Space | Activates focused action or dismiss button |
99
+ | Escape | Dismisses the toast (pauses timer first if hovering) |
100
+
101
+ Toast container itself is not focusable. Only interactive children (action, dismiss) receive focus.
102
+
103
+ ### Accessibility Rules
104
+
105
+ - `role="alert"` on warning/error toasts causes immediate announcement by screen readers — do NOT use for info/success (too noisy)
106
+ - `role="status"` on info/success uses a polite live region — announced at next opportunity
107
+ - The live region container MUST be present in the DOM before the toast text is injected — injecting `role="alert"` dynamically may not announce *(WCAG 4.1.3)*
108
+ - Dismiss button MUST have `aria-label="Dismiss notification"` or similar — the ✕ icon alone is not an accessible name
109
+ - Auto-dismiss timer MUST pause when the toast is hovered or focused *(WCAG 2.2.1 — Timing Adjustable)*
110
+ - Error toasts MUST either be persistent or have an explicit dismiss mechanism *(Polaris, Carbon)*
111
+
112
+ Cross-link: `reference/accessibility.md` — live-regions section
113
+
114
+ ---
115
+
116
+ ## Motion
117
+
118
+ | Transition | Duration | Easing | Notes |
119
+ |------------|----------|--------|-------|
120
+ | Enter (slide-in + fade) | 200ms | ease-out | Slides from bottom-right edge inward |
121
+ | Exit (slide-out + fade) | 150ms | ease-in | Reverse direction on dismiss |
122
+ | Stack reflow | 150ms | ease-out | Other toasts shift position when one exits |
123
+
124
+ **BAN**: Bouncing or spring physics on enter — toast is informational, not celebratory. Avoid `transition: all` (catches layout shifts during stack reflow).
125
+
126
+ Cross-link: `reference/motion.md` — `prefers-reduced-motion`: skip slide, use fade-only at 100ms
127
+
128
+ ---
129
+
130
+ ## Do / Don't
131
+
132
+ ### Do
133
+ - Use `role="status"` for info/success and `role="alert"` for warning/error *(WAI-ARIA APG)*
134
+ - Pre-mount the live region container in the DOM before injecting toast content *(WCAG 4.1.3)*
135
+ - Pause auto-dismiss timer on hover and focus *(WCAG 2.2.1)*
136
+ - Keep message text ≤80 characters; use action button for follow-up *(Material 3, Polaris)*
137
+
138
+ ### Don't
139
+ - Don't use toast for errors that require user action — use a modal or alert *(Material 3, Carbon)*
140
+ - Don't stack more than 3 toasts — queue the rest *(Radix, Polaris)*
141
+ - Don't put more than one action in a toast — use a modal for complex decisions *(Material 3)*
142
+ - Don't rely on toast alone for critical status — supplement with in-page feedback *(Carbon)*
143
+
144
+ ---
145
+
146
+ ## Anti-patterns Cross-links
147
+
148
+ | Anti-pattern | Entry |
149
+ |--------------|-------|
150
+ | Missing live region role | `reference/anti-patterns.md#ban-live-region` |
151
+ | Auto-dismiss without pause on hover | `reference/anti-patterns.md#ban-timing` |
152
+
153
+ ---
154
+
155
+ ## Benchmark Citations
156
+
157
+ | Claim | Sources |
158
+ |-------|---------|
159
+ | role="alert" for error/warning; role="status" for info/success | WAI-ARIA APG, Material 3, Carbon, Polaris |
160
+ | Auto-dismiss 4–8s; error toasts persistent | Radix, Material 3, Polaris, Carbon |
161
+ | Max 3 visible toasts, queue rest | Radix, Polaris |
162
+ | Pause timer on hover/focus (WCAG 2.2.1) | WAI-ARIA APG |
163
+ | Pre-mount live region before injecting text | WCAG 4.1.3 |
164
+ | UUPM save-confirmation / settings-saved patterns | UUPM app-interface (MIT) |
165
+
166
+ Full system URLs: `connections/design-corpora.md`
167
+
168
+ ---
169
+
170
+ ## Grep Signatures
171
+
172
+ ```bash
173
+ # Error toast missing role="alert" — uses role="status" instead
174
+ grep -rn 'toast\|Toast\|snackbar' src/ | grep 'error\|Error\|danger' | grep -v 'role="alert"'
175
+
176
+ # Toast missing any role attribute
177
+ grep -rn 'toast\|Toast\|snackbar' src/ | grep -v 'role='
178
+
179
+ # Missing aria-live region (live region not pre-mounted)
180
+ grep -rn 'toast\|Toast' src/ | grep -v 'aria-live\|role="status"\|role="alert"'
181
+
182
+ # Dismiss button missing aria-label
183
+ grep -rn 'toast.*close\|toast.*dismiss\|close.*toast' src/ | grep -v 'aria-label'
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Failing Example
189
+
190
+ ```html
191
+ <!-- BAD: toast with no role — screen readers receive no announcement -->
192
+ <div class="toast toast--error">
193
+ Settings failed to save.
194
+ <button class="toast__close">✕</button>
195
+ </div>
196
+ ```
197
+
198
+ **Why it fails**: No `role="alert"` so screen readers do not announce the error. The ✕ dismiss button has no `aria-label`. There is no live region for the text injection.
199
+ **Grep detection**: `grep -rn 'class.*toast' src/ | grep -v 'role='`
200
+ **Fix**: Add `role="alert"` for error severity; add `aria-label="Dismiss notification"` to the close button; pre-mount `<div role="alert" aria-live="assertive">` in the document and inject content into it.
@@ -0,0 +1,201 @@
1
+ # Tooltip — Benchmark Spec
2
+
3
+ **Harvested from**: WAI-ARIA APG, Radix UI, Material 3, Carbon, Mantine, Fluent 2, Atlassian, Apple HIG
4
+ **Wave**: 2 · **Category**: Containers
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ A tooltip is a small, non-interactive label that appears on hover or keyboard focus to provide supplemental context for an element (typically an icon button or truncated text). It disappears on mouse-out, blur, or Escape. Tooltips MUST NOT contain interactive content (buttons, links, form elements). For interactive overlay content, use Popover. *(WAI-ARIA APG, Radix, Carbon all enforce this boundary)*
11
+
12
+ ---
13
+
14
+ ## Anatomy
15
+
16
+ ```
17
+ [Icon button] ← hover or focus triggers tooltip
18
+
19
+ ▼ (after 300ms delay)
20
+ ┌────────────┐
21
+ │ Label │ ← role="tooltip"; 12px; no interactive content
22
+ └────────────┘
23
+ ▲ (optional 6px arrow caret)
24
+ ```
25
+
26
+ | Part | Required | Notes |
27
+ |------|----------|-------|
28
+ | Trigger | Yes | Usually a button or interactive element |
29
+ | Tooltip container | Yes | `role="tooltip"` + unique `id` |
30
+ | Label text | Yes | Short (≤60 chars), descriptive |
31
+ | Arrow | No | 6–8px caret indicating anchor |
32
+
33
+ ---
34
+
35
+ ## Variants
36
+
37
+ | Variant | Description | Systems |
38
+ |---------|-------------|---------|
39
+ | Default | Hover + focus triggered | All |
40
+ | Delayed | 300ms show delay; 0ms hide | All (WAI-ARIA APG recommended) |
41
+ | Instant | No delay (icon toolbars, dense UIs) | Material 3, Carbon |
42
+ | Dark | Dark background regardless of theme | Most systems |
43
+ | Light | Light background | Mantine (inverted) |
44
+
45
+ **Norm** (≥7/18): 300ms show delay, 0ms hide; max-width 240px; never interactive content.
46
+ **Diverge**: delay duration — Carbon recommends 100ms for toolbars; WAI-ARIA APG recommends ≤500ms. 300ms is the safe default.
47
+
48
+ ---
49
+
50
+ ## States
51
+
52
+ | State | Trigger | ARIA |
53
+ |-------|---------|------|
54
+ | hidden | default | `role="tooltip"` hidden (not in tab order) |
55
+ | visible (hover) | `mouseenter` (after delay) | `aria-describedby` on trigger points to tooltip id |
56
+ | visible (focus) | `focusin` on trigger | same |
57
+ | dismissed | `mouseleave`, `blur`, Escape | Tooltip hidden |
58
+
59
+ ---
60
+
61
+ ## Sizing & Spacing
62
+
63
+ | Property | Value | Notes |
64
+ |----------|-------|-------|
65
+ | Max width | 240px | *(Material 3: 200px, Carbon: 288px, Fluent: 240px)* |
66
+ | Padding | 6px 12px | |
67
+ | Font size | 12px/400 | Smaller than body to signal supplemental role |
68
+ | Border radius | 4–6px | Tight radius vs. card; feels like label |
69
+ | Offset from trigger | 6–8px | |
70
+
71
+ ---
72
+
73
+ ## Typography
74
+
75
+ - 12px/400 — tooltip text is supplemental; smaller weight and size distinguish it from primary content
76
+ - No bold, no headings inside tooltip — it is a single line of text (≤60 chars preferred)
77
+ - Multi-line: allowed if content genuinely requires it; still no interactive elements
78
+
79
+ ---
80
+
81
+ ## Keyboard & Accessibility
82
+
83
+ > **WAI-ARIA role**: `tooltip`
84
+ > **Trigger attributes**: `aria-describedby="tooltip-id"` — links the supplemental label
85
+
86
+ ### Keyboard Contract
87
+
88
+ *Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/ — W3C — 2024*
89
+
90
+ | Key | Action |
91
+ |-----|--------|
92
+ | Tab | (on trigger) Shows tooltip when trigger receives focus |
93
+ | Escape | Hides the tooltip |
94
+ | Tab / Shift+Tab | Hides tooltip when focus leaves the trigger |
95
+
96
+ Tooltip does NOT receive focus. It is purely a visual label attached to the trigger.
97
+
98
+ ### Accessibility Rules
99
+
100
+ - Trigger MUST have `aria-describedby` pointing to the tooltip's `id` — screen readers read tooltip content as supplemental description
101
+ - Tooltip is `role="tooltip"` — NOT `role="dialog"` (no interactivity, no focus trap)
102
+ - Tooltip MUST appear on keyboard focus, not only on hover — keyboard-only users need access too *(WCAG 1.3.3, 2.1.1)*
103
+ - Do NOT put interactive content inside a tooltip — use Popover (`reference/components/popover.md`)
104
+ - Do NOT use tooltip as the only accessible name for a control — use `aria-label` on the trigger instead; tooltip supplements, not replaces, the accessible name
105
+ - Escape MUST dismiss the tooltip without removing focus from the trigger *(WAI-ARIA APG)*
106
+
107
+ ---
108
+
109
+ ## Motion
110
+
111
+ | Transition | Duration | Easing | Notes |
112
+ |------------|----------|--------|-------|
113
+ | Show | 100ms | ease-out | Fade only; no scale (too flashy for a label) |
114
+ | Hide | 80ms | ease | Fade only; immediate on Escape |
115
+ | Delay | 300ms | — | CSS `transition-delay` or JS timeout |
116
+
117
+ Cross-link: `reference/motion.md` — `prefers-reduced-motion`: skip fade, instant show/hide
118
+
119
+ ---
120
+
121
+ ## Do / Don't
122
+
123
+ ### Do
124
+ - Show on keyboard focus AND hover — not hover-only *(WAI-ARIA APG, WCAG 2.1.1)*
125
+ - Use `aria-describedby` to link trigger to tooltip *(WAI-ARIA APG)*
126
+ - Limit tooltip content to ≤60 chars — longer content belongs in a popover *(Carbon, Material 3)*
127
+ - Apply 300ms show delay to prevent accidental triggers while cursor passes *(WAI-ARIA APG, Carbon)*
128
+
129
+ ### Don't
130
+ - Don't put interactive elements inside a tooltip *(WAI-ARIA APG — this makes it a popover)*
131
+ - Don't use tooltip as the only accessible name — `aria-describedby` supplements, not replaces, `aria-label` *(WAI-ARIA APG)*
132
+ - Don't trigger tooltip on click — use a popover *(Radix, WAI-ARIA APG)*
133
+ - Don't use tooltip for critical information — it's supplemental; users on touch devices may miss it *(Material 3, Polaris)*
134
+
135
+ ---
136
+
137
+ ## Anti-patterns Cross-links
138
+
139
+ | Anti-pattern | Entry |
140
+ |--------------|-------|
141
+ | Interactive content inside tooltip | `reference/anti-patterns.md` |
142
+ | Hover-only tooltip (not focus-triggered) | `reference/anti-patterns.md` |
143
+
144
+ ---
145
+
146
+ ## Benchmark Citations
147
+
148
+ | Claim | Sources |
149
+ |-------|---------|
150
+ | role="tooltip" not role="dialog" | WAI-ARIA APG |
151
+ | Show on focus AND hover | WAI-ARIA APG, WCAG 2.1.1 |
152
+ | Escape dismisses without removing focus | WAI-ARIA APG §3.2 |
153
+ | 300ms delay | WAI-ARIA APG, Carbon |
154
+ | 240px max-width | Material 3, Fluent 2 |
155
+ | No interactive content | WAI-ARIA APG (all systems agree) |
156
+
157
+ Full system URLs: `connections/design-corpora.md`
158
+
159
+ ---
160
+
161
+ ## Grep Signatures
162
+
163
+ ```bash
164
+ # Tooltip triggered only on hover (not focus) — missing focusin handler
165
+ grep -rn 'tooltip\|Tooltip' src/ | grep 'mouseenter\|onHover' | grep -v 'focus\|onFocus'
166
+
167
+ # Interactive content inside tooltip
168
+ grep -rn 'role="tooltip"' src/ | xargs grep -l 'button\|<a \|input' 2>/dev/null
169
+
170
+ # Trigger missing aria-describedby
171
+ grep -rn 'role="tooltip"' src/ | grep -v 'aria-describedby'
172
+
173
+ # Tooltip without id (aria-describedby target requires id)
174
+ grep -rn 'role="tooltip"' src/ | grep -v ' id='
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Failing Example
180
+
181
+ ```html
182
+ <!-- BAD: tooltip shows on hover only, no focus trigger, no ARIA linkage -->
183
+ <button class="icon-btn" onmouseenter="showTooltip()" onmouseleave="hideTooltip()">
184
+ <svg><!-- settings icon --></svg>
185
+ </button>
186
+ <div class="tooltip">Settings</div>
187
+ ```
188
+
189
+ **Why it fails**: Keyboard users never see the tooltip. Screen readers receive no supplemental description. The `<div>` has no `role="tooltip"` and no `id`, so `aria-describedby` cannot link to it.
190
+ **Grep detection**: `grep -rn 'mouseenter\|onHover' src/ | grep -i 'tooltip' | grep -v 'focus'`
191
+ **Fix**:
192
+ ```html
193
+ <button aria-describedby="settings-tip"
194
+ onmouseenter="show()" onmouseleave="hide()"
195
+ onfocusin="show()" onfocusout="hide()"
196
+ onkeydown="e.key==='Escape'&&hide()">
197
+ <svg aria-hidden="true"><!-- icon --></svg>
198
+ <span class="sr-only">Settings</span>
199
+ </button>
200
+ <div role="tooltip" id="settings-tip">Manage account settings</div>
201
+ ```
@@ -0,0 +1,225 @@
1
+ # Tree View (Hierarchical Navigation) — Benchmark Spec
2
+
3
+ **Harvested from**: WAI-ARIA APG, Carbon, Atlassian, Radix, Material 3
4
+ **Wave**: 4 · **Category**: Navigation & Data
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ A tree view displays hierarchical data in a collapsible structure of parent and child nodes. It is used for file systems, organisational charts, nested settings, and category navigation. Each node can be expanded (revealing children) or collapsed. Nodes may be selectable, checkable, or action-only. *(WAI-ARIA APG Tree View, Carbon TreeView, Atlassian Tree, Radix all define the tree as a hierarchical, keyboard-navigable disclosure widget)*
11
+
12
+ ---
13
+
14
+ ## Anatomy
15
+
16
+ ```
17
+ <ul role="tree" aria-label="File explorer">
18
+ <li role="treeitem" aria-expanded="true" aria-level="1">
19
+ <span>📁 src</span>
20
+ <ul role="group">
21
+ <li role="treeitem" aria-level="2" aria-selected="false">
22
+ <span>📄 index.ts</span>
23
+ </li>
24
+ <li role="treeitem" aria-expanded="false" aria-level="2">
25
+ <span>📁 components</span>
26
+ <ul role="group"> <!-- hidden when collapsed -->
27
+ <li role="treeitem" aria-level="3">📄 Button.tsx</li>
28
+ </ul>
29
+ </li>
30
+ </ul>
31
+ </li>
32
+ </ul>
33
+ ```
34
+
35
+ | Part | Required | Notes |
36
+ |------|----------|-------|
37
+ | `role="tree"` root | Yes | On the outermost list; `aria-label` or `aria-labelledby` |
38
+ | `role="treeitem"` | Yes | On every node `<li>` |
39
+ | `role="group"` | Yes (on child lists) | Child `<ul>` inside an expandable `treeitem` |
40
+ | `aria-expanded` | Required on expandable nodes | `"true"` / `"false"`; omit on leaf nodes |
41
+ | `aria-level` | Yes | Nesting depth; starts at 1 |
42
+ | `aria-selected` | Yes on selectable trees | `"true"` / `"false"` on each treeitem |
43
+ | `aria-multiselectable` | No | `"true"` on `role="tree"` for multi-select |
44
+ | `aria-busy` | Conditional | `"true"` on node while fetching children (lazy load) |
45
+
46
+ ---
47
+
48
+ ## Variants
49
+
50
+ | Variant | Description | Systems |
51
+ |---------|-------------|---------|
52
+ | Default | Expand/collapse only; no selection | WAI-ARIA APG, Carbon |
53
+ | Single-select | One item selectable; `aria-selected` | WAI-ARIA APG, Carbon, Radix |
54
+ | Multi-select | Multiple items selectable; `aria-multiselectable` | WAI-ARIA APG, Carbon |
55
+ | Checkbox tree | Each node has a checkbox; indeterminate state for partial parent | Carbon, Material 3 |
56
+ | Lazy-loaded | Children fetched on expand; `aria-busy` during fetch | Carbon, Atlassian |
57
+ | File explorer | File/folder icons; drag-to-reorder | Carbon, Radix |
58
+
59
+ **Norm** (≥4 systems agree): `role="tree"` + `role="treeitem"` + `role="group"` on child lists; `aria-expanded` on parent nodes; keyboard navigation with arrow keys.
60
+ **Diverge**: Carbon renders `aria-level` as a data attribute for CSS depth indentation; Radix computes `aria-level` implicitly from DOM nesting depth.
61
+
62
+ ---
63
+
64
+ ## States
65
+
66
+ | State | Trigger | Visual | ARIA |
67
+ |-------|---------|--------|------|
68
+ | node-default | — | Icon + label; indented by level | — |
69
+ | node-hover | pointer over | 8% overlay on row | — |
70
+ | node-focus | keyboard focus | 2px focus-visible ring on row | `tabindex="0"` on focused; `-1` on rest |
71
+ | node-selected | click or Enter/Space | Filled row highlight | `aria-selected="true"` |
72
+ | node-expanded | toggle click or ArrowRight | Children visible; icon rotated/open | `aria-expanded="true"` |
73
+ | node-collapsed | toggle click or ArrowLeft | Children hidden; icon closed | `aria-expanded="false"` |
74
+ | node-loading | expand triggers fetch | Spinner icon beside label | `aria-busy="true"` |
75
+ | node-disabled | disabled prop | 38% opacity; cursor: default | `aria-disabled="true"` |
76
+
77
+ ---
78
+
79
+ ## Sizing & Spacing
80
+
81
+ | Element | Value | Notes |
82
+ |---------|-------|-------|
83
+ | Node height | 32–36px | Compact tree; denser than lists |
84
+ | Level indent | 16–20px per level | CSS left-padding on `role="group"` |
85
+ | Expand icon | 16px | Chevron or ▶ triangle; rotates on expand |
86
+ | Node icon | 16px | File/folder/custom; left of label |
87
+ | Max nesting depth (recommended) | 5–6 levels | Deeper trees become hard to navigate |
88
+
89
+ **Norm**: 16–20px per level indentation (Carbon, Atlassian, Radix). 32–36px node height for compact data.
90
+
91
+ ---
92
+
93
+ ## Typography
94
+
95
+ - Node label: body-sm (13–14px), weight 400; selected node weight 500
96
+ - Level depth: visual indentation only — no font-size reduction per level
97
+ - Disabled node: same font size, `color: --text-disabled`
98
+
99
+ Cross-link: `reference/typography.md` — body-sm
100
+
101
+ ---
102
+
103
+ ## Keyboard & Accessibility
104
+
105
+ > **WAI-ARIA role**: `tree` (root), `treeitem` (each node), `group` (child list)
106
+ > **Required attributes**: `aria-expanded` on expandable nodes; `aria-level` on each treeitem; `aria-selected` on selectable treeitems; `aria-label` on root `role="tree"`
107
+
108
+ ### Keyboard Contract
109
+
110
+ *Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/treeview/ — W3C — 2024*
111
+
112
+ | Key | Action |
113
+ |-----|--------|
114
+ | ArrowDown | Moves focus to next visible treeitem (skips collapsed children) |
115
+ | ArrowUp | Moves focus to previous visible treeitem |
116
+ | ArrowRight | If collapsed: expands node. If expanded: moves focus to first child |
117
+ | ArrowLeft | If expanded: collapses node. If collapsed: moves focus to parent |
118
+ | Home | Moves focus to first treeitem in tree |
119
+ | End | Moves focus to last visible treeitem in tree |
120
+ | Enter / Space | Selects or activates focused treeitem |
121
+ | Asterisk (*) | Expands all siblings at the same level |
122
+ | A–Z / a–z | Jumps to next treeitem matching typed character |
123
+
124
+ ### Accessibility Rules
125
+
126
+ - ALL expandable nodes MUST have `aria-expanded` — CSS-only expand/collapse is invisible to AT
127
+ - `aria-level` must accurately reflect nesting depth (1-based) on every `role="treeitem"`
128
+ - Child `<ul>` MUST have `role="group"` — without it, AT cannot perceive the parent-child relationship
129
+ - Focus management uses roving `tabindex`: only the active node has `tabindex="0"`; all others `tabindex="-1"`
130
+ - Lazy-loaded nodes MUST set `aria-busy="true"` while fetching; remove when complete
131
+ - Multi-select tree MUST set `aria-multiselectable="true"` on the `role="tree"` element
132
+
133
+ Cross-link: `reference/accessibility.md` — tree pattern, roving tabindex, aria-busy
134
+
135
+ ---
136
+
137
+ ## Motion
138
+
139
+ | Transition | Duration | Easing | Notes |
140
+ |------------|----------|--------|-------|
141
+ | Expand children | 150ms | ease-out | Height expand via `overflow: hidden` |
142
+ | Collapse children | 120ms | ease-in | Height collapse |
143
+ | Chevron rotate | 150ms | ease-in-out | 0° → 90° on expand |
144
+ | Node selection highlight | 100ms | ease-out | Background color only |
145
+ | Lazy-load spinner | continuous | linear | Replace with content on load |
146
+
147
+ **BAN**: Do not use CSS-only `display: none` / `display: block` to toggle children without updating `aria-expanded` — both changes must happen atomically.
148
+
149
+ Cross-link: `reference/motion.md` — disclosure animations
150
+
151
+ ---
152
+
153
+ ## Do / Don't
154
+
155
+ ### Do
156
+ - Use `role="tree"` + `role="treeitem"` + `role="group"` on every tree *(WAI-ARIA APG)*
157
+ - Update `aria-expanded` in the same event handler that toggles child visibility *(WAI-ARIA APG)*
158
+ - Use `aria-busy="true"` on a node while its children are loading *(WAI-ARIA APG, Carbon)*
159
+ - Limit tree depth to 5–6 levels to prevent cognitive overload *(Carbon, Atlassian HIG)*
160
+
161
+ ### Don't
162
+ - Don't use `<ul>/<li>` tree without `role="tree"` + `role="treeitem"` — semantically invisible to AT *(WCAG 1.3.1)*
163
+ - Don't forget `role="group"` on child `<ul>` — AT cannot infer parent-child structure without it *(WAI-ARIA APG)*
164
+ - Don't manage expand/collapse with CSS only (no `aria-expanded`) — blind users cannot discover collapsed state *(WCAG 4.1.2)*
165
+ - Don't indent via `aria-level` alone — also apply visual CSS indentation for sighted users *(Material 3, Carbon)*
166
+
167
+ ---
168
+
169
+ ## Anti-patterns Cross-links
170
+
171
+ | Anti-pattern | Entry |
172
+ |--------------|-------|
173
+ | BAN-04 | `transition: all` on tree nodes — `reference/anti-patterns.md#ban-04` |
174
+
175
+ ---
176
+
177
+ ## Benchmark Citations
178
+
179
+ | Claim | Sources |
180
+ |-------|---------|
181
+ | role="tree" + role="treeitem" + role="group" structure | WAI-ARIA APG tree pattern |
182
+ | aria-expanded required on all expandable nodes | WAI-ARIA APG, Carbon, Atlassian |
183
+ | aria-level for nesting depth | WAI-ARIA APG |
184
+ | Roving tabindex focus management | WAI-ARIA APG |
185
+ | aria-busy="true" during lazy load | WAI-ARIA APG, Carbon |
186
+
187
+ Full system URLs: `connections/design-corpora.md`
188
+
189
+ ---
190
+
191
+ ## Grep Signatures
192
+
193
+ ```bash
194
+ # <ul>/<li> tree without role="tree" + role="treeitem"
195
+ grep -rn 'tree\|file.*explorer\|folder.*tree' src/ | grep '<ul\|<li' | grep -v 'role="tree"\|role="treeitem"'
196
+
197
+ # Expandable node missing aria-expanded
198
+ grep -rn 'role="treeitem"' src/ | grep 'expand\|collaps' | grep -v 'aria-expanded'
199
+
200
+ # Child list missing role="group"
201
+ grep -rn 'role="treeitem"' src/ | grep -A 2 'aria-expanded' | grep '<ul' | grep -v 'role="group"'
202
+
203
+ # Tree missing aria-label
204
+ grep -rn 'role="tree"' src/ | grep -v 'aria-label\|aria-labelledby'
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Failing Example
210
+
211
+ ```html
212
+ <!-- BAD: CSS-only expand/collapse with no aria-expanded updates -->
213
+ <ul class="tree">
214
+ <li class="tree-node has-children expanded"> <!-- CSS class only; no ARIA -->
215
+ <span onclick="toggle(this)">📁 src</span>
216
+ <ul class="children"> <!-- no role="group" -->
217
+ <li class="tree-node">📄 index.ts</li>
218
+ </ul>
219
+ </li>
220
+ </ul>
221
+ ```
222
+
223
+ **Why it fails**: No `role="tree"` or `role="treeitem"`; no `aria-expanded` — screen readers cannot tell if the node is open or closed; `role="group"` missing on child list; no `aria-level`; expand/collapse is click-only (no keyboard).
224
+ **Grep detection**: `grep -rn 'class.*tree\|treeview' src/ | grep '<ul\|<li' | grep -v 'role='`
225
+ **Fix**: Add `role="tree"` to root `<ul>`; `role="treeitem"` + `aria-expanded` + `aria-level` to each node; `role="group"` to child `<ul>`; implement arrow-key navigation with roving tabindex.