@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,229 @@
|
|
|
1
|
+
# Table (Data Table / Data Grid) — Benchmark Spec
|
|
2
|
+
|
|
3
|
+
**Harvested from**: Carbon DataTable, Polaris DataTable, Atlassian DynamicTable, Ant Design Table, UUPM (app-interface, MIT)
|
|
4
|
+
**Wave**: 4 · **Category**: Navigation & Data
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
A data table presents structured, comparable information in rows and columns. It supports sorting, filtering, row selection, and pagination. Use `role="table"` for static display; use `role="grid"` for interactive tables where keyboard navigation between cells is needed (e.g., spreadsheet-like editing). Tables are distinct from Lists (unstructured items) and Cards (single-entity display). *(Carbon DataTable, Polaris DataTable, Atlassian DynamicTable, Ant Table all define table as the canonical multi-column data display)*
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Anatomy
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
┌─────────────────────────────────────────────────────┐
|
|
18
|
+
│ [☐] Name ↑ Status Amount Actions │ <thead>
|
|
19
|
+
│─────────────────────────────────────────────────────│
|
|
20
|
+
│ [☐] Alice Chen Active $1,200.00 [···] │ <tbody>
|
|
21
|
+
│ [☑] Bob Tanaka Inactive $850.00 [···] │ aria-selected="true"
|
|
22
|
+
│ [☐] Carol Wu Active $2,400.00 [···] │
|
|
23
|
+
│─────────────────────────────────────────────────────│
|
|
24
|
+
│ Showing 1–3 of 247 [‹ Prev] 1 2 3 [Next ›] │ <tfoot>
|
|
25
|
+
└─────────────────────────────────────────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Part | Required | Notes |
|
|
29
|
+
|------|----------|-------|
|
|
30
|
+
| `<table>` | Yes | Semantic table element; `role="grid"` if interactive |
|
|
31
|
+
| `<caption>` | Yes (or `aria-label`) | Describes the table's purpose; `<caption>` preferred |
|
|
32
|
+
| `<thead>` | Yes | Column header row(s) |
|
|
33
|
+
| `<th scope="col">` | Yes | `scope="col"` on every column header |
|
|
34
|
+
| `<tbody>` | Yes | Data rows |
|
|
35
|
+
| `<th scope="row">` | No | Row header for the first cell if rows have identity |
|
|
36
|
+
| `<tfoot>` | No | Summary row, pagination, totals |
|
|
37
|
+
| Sortable header | No | `aria-sort="ascending|descending|none"` on `<th>` |
|
|
38
|
+
| Select-all checkbox | No | `<th scope="col">` with select-all; `aria-label="Select all rows"` |
|
|
39
|
+
| Row checkbox | No | `<td>` with checkbox; selected row has `aria-selected="true"` on `<tr>` |
|
|
40
|
+
| Scroll wrapper | Conditional | `overflow-x: auto` + `tabindex="0"` on wrapper for keyboard scroll |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Variants
|
|
45
|
+
|
|
46
|
+
| Variant | Description | Systems |
|
|
47
|
+
|---------|-------------|---------|
|
|
48
|
+
| Static / display | Read-only; `role="table"` | All systems |
|
|
49
|
+
| Sortable | Clickable column headers; `aria-sort` | Carbon, Polaris, Atlassian, Ant |
|
|
50
|
+
| Selectable | Row checkboxes; batch actions toolbar | Carbon, Polaris, Atlassian |
|
|
51
|
+
| Expandable rows | Toggle row detail panel | Carbon, Ant, Atlassian |
|
|
52
|
+
| Interactive / grid | Cell-level focus; `role="grid"` | Carbon, Ant |
|
|
53
|
+
| Sticky header | `position: sticky` on `<thead>` | Carbon, Ant, Polaris |
|
|
54
|
+
| Master-detail | Table + side detail pane | UUPM app-interface (MIT) |
|
|
55
|
+
| Dashboard data grid | Dense, compact variant for analytics dashboards | UUPM app-interface (MIT) |
|
|
56
|
+
|
|
57
|
+
**Norm** (≥4 systems agree): `<table>` with `<thead>`/`<tbody>`; `scope="col"` on all `<th>`; `aria-sort` on sortable columns.
|
|
58
|
+
**Diverge**: Carbon uses `role="grid"` by default for keyboard cell navigation; Polaris uses `role="table"` for read-only display.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## States
|
|
63
|
+
|
|
64
|
+
| State | Trigger | Visual | ARIA |
|
|
65
|
+
|-------|---------|--------|------|
|
|
66
|
+
| default | — | Alternating row colors or border separators | — |
|
|
67
|
+
| row-hover | pointer over row | Subtle row highlight (4% overlay) | — |
|
|
68
|
+
| row-selected | checkbox checked | Row highlight; checkbox filled | `aria-selected="true"` on `<tr>` |
|
|
69
|
+
| header-sort-asc | sort click | Arrow indicator up; column highlight | `aria-sort="ascending"` on `<th>` |
|
|
70
|
+
| header-sort-desc | sort click again | Arrow indicator down | `aria-sort="descending"` on `<th>` |
|
|
71
|
+
| header-sort-none | default or reset | No indicator | `aria-sort="none"` on sortable `<th>` |
|
|
72
|
+
| header-focus | keyboard focus on sortable `<th>` | 2px focus-visible ring | — |
|
|
73
|
+
| loading | data fetch | Skeleton rows or spinner overlay | `aria-busy="true"` on table or container |
|
|
74
|
+
| empty | no results | Empty state illustration + message | — |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Sizing & Spacing
|
|
79
|
+
|
|
80
|
+
| Density | Row height | Cell padding H | Cell padding V | Font |
|
|
81
|
+
|---------|------------|----------------|----------------|------|
|
|
82
|
+
| compact | 32px | 12px | 4px | 13px |
|
|
83
|
+
| default | 48px | 16px | 12px | 14px |
|
|
84
|
+
| comfortable | 56px | 20px | 16px | 14px |
|
|
85
|
+
|
|
86
|
+
**Norm**: 48px default row height (Carbon, Atlassian, Ant). Column min-width 80px; text columns flexible.
|
|
87
|
+
Virtualise rows when `rowCount > 200`; use TanStack Virtual or react-virtual.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Typography
|
|
92
|
+
|
|
93
|
+
- Column header: body-sm or label-sm (12–13px), weight 600, `color: --text-secondary`
|
|
94
|
+
- Cell text: body-sm (13–14px), weight 400
|
|
95
|
+
- Numeric cells: `font-variant-numeric: tabular-nums` — column values align on decimal point
|
|
96
|
+
- Truncation: `text-overflow: ellipsis` on cells with `max-width`; tooltip reveals full value on hover
|
|
97
|
+
|
|
98
|
+
Cross-link: `reference/typography.md` — tabular-nums, body-sm
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Keyboard & Accessibility
|
|
103
|
+
|
|
104
|
+
> **WAI-ARIA role**: `table` (static) or `grid` (interactive)
|
|
105
|
+
> **Required attributes**: `scope="col"` on all `<th>` headers; `aria-sort` on sortable columns; `aria-selected="true"` on selected `<tr>`; `<caption>` or `aria-label` on `<table>`
|
|
106
|
+
|
|
107
|
+
### Keyboard Contract
|
|
108
|
+
|
|
109
|
+
*Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/grid/ — W3C — 2024*
|
|
110
|
+
|
|
111
|
+
| Key | Action (role="grid") |
|
|
112
|
+
|-----|---------------------|
|
|
113
|
+
| ArrowRight | Moves focus to next cell in row |
|
|
114
|
+
| ArrowLeft | Moves focus to previous cell in row |
|
|
115
|
+
| ArrowDown | Moves focus to same cell in next row |
|
|
116
|
+
| ArrowUp | Moves focus to same cell in previous row |
|
|
117
|
+
| Home | Moves focus to first cell in row |
|
|
118
|
+
| End | Moves focus to last cell in row |
|
|
119
|
+
| Ctrl+Home | Moves focus to first cell in grid |
|
|
120
|
+
| Ctrl+End | Moves focus to last cell in grid |
|
|
121
|
+
| Enter / Space | Activates cell widget (checkbox, link, button) |
|
|
122
|
+
| Tab | Moves focus out of grid to next component |
|
|
123
|
+
|
|
124
|
+
### Accessibility Rules
|
|
125
|
+
|
|
126
|
+
- ALL `<th>` column headers MUST have `scope="col"` — missing scope breaks AT table navigation
|
|
127
|
+
- Sortable `<th>` elements MUST have `aria-sort` with value `ascending`, `descending`, or `none`
|
|
128
|
+
- Selected rows MUST use `aria-selected="true"` on `<tr>` — CSS class alone is invisible to AT
|
|
129
|
+
- `<table>` MUST have a `<caption>` or `aria-label` to announce the table's purpose
|
|
130
|
+
- The responsive scroll wrapper MUST have `tabindex="0"` so keyboard users can scroll horizontally
|
|
131
|
+
- `role="grid"` enables cell-level arrow-key navigation; use only when cells contain interactive controls
|
|
132
|
+
|
|
133
|
+
Cross-link: `reference/accessibility.md` — table semantics, grid pattern
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Motion
|
|
138
|
+
|
|
139
|
+
| Transition | Duration | Easing | Notes |
|
|
140
|
+
|------------|----------|--------|-------|
|
|
141
|
+
| Row hover highlight | 80ms | ease-out | Background color only |
|
|
142
|
+
| Row select | 100ms | ease-out | Checkbox + row color |
|
|
143
|
+
| Sort indicator change | 120ms | ease-out | Arrow direction transition |
|
|
144
|
+
| Expandable row open | 150ms | ease-out | Height expand |
|
|
145
|
+
| Skeleton shimmer | 1500ms | linear loop | Loading placeholder |
|
|
146
|
+
|
|
147
|
+
**BAN**: Do not animate `width` on table columns — causes full table relayout on every frame.
|
|
148
|
+
|
|
149
|
+
Cross-link: `reference/motion.md` — layout-affecting transitions, skeleton shimmer
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Do / Don't
|
|
154
|
+
|
|
155
|
+
### Do
|
|
156
|
+
- Add `scope="col"` to every `<th>` — screen readers use this to announce column context *(WAI-ARIA, Carbon)*
|
|
157
|
+
- Use `aria-sort` on sortable headers — not just a visual arrow icon *(Carbon, Polaris, Atlassian)*
|
|
158
|
+
- Use `tabindex="0"` on horizontal scroll wrapper for keyboard accessibility *(WCAG 2.1.1)*
|
|
159
|
+
- Virtualise rows at > 200 items — prevents browser paint lag *(Carbon, Ant)*
|
|
160
|
+
|
|
161
|
+
### Don't
|
|
162
|
+
- Don't build a "table" with `<div>` elements and no ARIA grid roles — invisible to AT *(WCAG 1.3.1)*
|
|
163
|
+
- Don't use `aria-sort` on non-sortable columns — misleads users into clicking non-interactive headers *(WAI-ARIA)*
|
|
164
|
+
- Don't place `overflow-x: auto` on `<table>` directly — wrap in a `<div>` with `tabindex="0"` *(WCAG 2.1.1)*
|
|
165
|
+
- Don't use `display: contents` on `<thead>/<tbody>/<tr>` — breaks AT table parsing *(WCAG 1.3.1)*
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Anti-patterns Cross-links
|
|
170
|
+
|
|
171
|
+
| Anti-pattern | Entry |
|
|
172
|
+
|--------------|-------|
|
|
173
|
+
| BAN-04 | `transition: all` on table cells — `reference/anti-patterns.md#ban-04` |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Benchmark Citations
|
|
178
|
+
|
|
179
|
+
| Claim | Sources |
|
|
180
|
+
|-------|---------|
|
|
181
|
+
| scope="col" on all th headers | WAI-ARIA, Carbon DataTable, Polaris |
|
|
182
|
+
| aria-sort on sortable columns | WAI-ARIA APG grid pattern, Carbon, Atlassian |
|
|
183
|
+
| aria-selected="true" on selected rows | WAI-ARIA APG, Carbon, Ant |
|
|
184
|
+
| role="grid" for interactive cell navigation | WAI-ARIA APG, Carbon |
|
|
185
|
+
| Virtualise at > 200 rows | Carbon, Ant (TanStack Virtual recommendation) |
|
|
186
|
+
|
|
187
|
+
Full system URLs: `connections/design-corpora.md`
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Grep Signatures
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# <th> missing scope attribute
|
|
195
|
+
grep -rn '<th' src/ | grep -v 'scope='
|
|
196
|
+
|
|
197
|
+
# Sortable column missing aria-sort
|
|
198
|
+
grep -rn 'sortable\|sort-header\|on.*sort' src/ | grep '<th' | grep -v 'aria-sort'
|
|
199
|
+
|
|
200
|
+
# Selected row missing aria-selected
|
|
201
|
+
grep -rn 'row.*selected\|selected.*row\|isSelected' src/ | grep '<tr' | grep -v 'aria-selected'
|
|
202
|
+
|
|
203
|
+
# Table built with divs (no semantic markup)
|
|
204
|
+
grep -rn 'class.*table\|data-table' src/ | grep '<div' | grep -v 'role="table"\|role="grid"'
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Failing Example
|
|
210
|
+
|
|
211
|
+
```html
|
|
212
|
+
<!-- BAD: <div> table with no semantic markup and no ARIA grid roles -->
|
|
213
|
+
<div class="data-table">
|
|
214
|
+
<div class="table-header">
|
|
215
|
+
<div class="col-header" onclick="sortByName()">Name</div> <!-- no aria-sort -->
|
|
216
|
+
<div class="col-header">Status</div>
|
|
217
|
+
<div class="col-header">Amount</div>
|
|
218
|
+
</div>
|
|
219
|
+
<div class="table-row selected"> <!-- no aria-selected -->
|
|
220
|
+
<div class="cell">Alice Chen</div>
|
|
221
|
+
<div class="cell">Active</div>
|
|
222
|
+
<div class="cell">$1,200.00</div>
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Why it fails**: No `<table>/<thead>/<tbody>/<th>/<td>` semantics; no `scope="col"` on headers; no `aria-sort` on sortable column; `selected` row uses CSS class without `aria-selected="true"`; screen readers cannot navigate by row/column; no caption/label.
|
|
228
|
+
**Grep detection**: `grep -rn 'data-table\|table.*container' src/ | grep '<div' | grep -v 'role='`
|
|
229
|
+
**Fix**: Replace with semantic `<table>` and `<th scope="col">` headers; add `aria-sort` to sortable headers; add `aria-selected="true"` to selected `<tr>`; add `<caption>` describing the table.
|
|
@@ -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,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.
|