@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,194 @@
1
+ # Switch — Benchmark Spec
2
+
3
+ **Harvested from**: Apple HIG, Material 3, Radix UI, Spectrum (Adobe), Polaris, Fluent 2, Mantine, shadcn/ui
4
+ **Wave**: 1 · **Category**: Inputs
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ A switch (toggle) represents an immediate binary action — changes take effect without a confirm step, like enabling dark mode or activating a feature. It differs from a checkbox in this immediacy: a checkbox is a form option submitted later; a switch acts now. Do not use a switch inside a form that requires a submit button to apply changes. *(Apple HIG, Material 3, Polaris all distinguish switch from checkbox this way)*
11
+
12
+ ---
13
+
14
+ ## Anatomy
15
+
16
+ ```
17
+ Label ← visible text describing the setting
18
+ ◉─────── ON ← track + thumb; thumb slides right on ON
19
+ ○─────── OFF ← thumb left on OFF
20
+ Helper text (opt.)
21
+ ```
22
+
23
+ | Part | Required | Notes |
24
+ |------|----------|-------|
25
+ | Track | Yes | Background bar; 28–52px wide, 14–32px tall |
26
+ | Thumb | Yes | Circular indicator that slides |
27
+ | Label | Yes (or `aria-label`) | Describes what the switch controls |
28
+ | State text (ON/OFF) | No | Optional inside or beside track; not required by a11y |
29
+ | Helper text | No | Clarifies effect of the switch |
30
+
31
+ ---
32
+
33
+ ## Variants
34
+
35
+ | Variant | Description | Systems |
36
+ |---------|-------------|---------|
37
+ | Default | Label left/right, switch right/left | All |
38
+ | With icon | Icon inside thumb (check/x) | Material 3, Fluent 2 |
39
+ | With state text | ON/OFF inside track | Apple HIG (iOS), Fluent 2 |
40
+ | Small | Compact size (24px height) | Mantine, shadcn |
41
+ | Large | 32px height | Material 3, Apple HIG |
42
+
43
+ **Norm** (≥5/18): label appears to the left of the switch (LTR); toggle is right-aligned.
44
+ **Diverge**: thumb icon vs. bare thumb — Material 3 adds check icon on ON, x on OFF; most others use bare thumb. Bare is simpler and more portable across themes.
45
+
46
+ ---
47
+
48
+ ## States
49
+
50
+ | State | Visual | ARIA |
51
+ |-------|--------|------|
52
+ | OFF (unchecked) | Thumb left, track muted | `aria-checked="false"` |
53
+ | ON (checked) | Thumb right, track colored | `aria-checked="true"` |
54
+ | hover | Thumb scale up slightly (1.1×) | — |
55
+ | focus | 2px focus ring around track | — |
56
+ | disabled OFF | 38% opacity, thumb left | `aria-disabled="true"` |
57
+ | disabled ON | 38% opacity, thumb right | `aria-disabled="true"` |
58
+
59
+ ---
60
+
61
+ ## Sizing & Spacing
62
+
63
+ | Size | Track W×H | Thumb diameter | Touch target |
64
+ |------|-----------|----------------|--------------|
65
+ | sm | 36×20px | 16px | 44×44px via pseudo-element |
66
+ | md (default) | 44×24px | 20px | 44×44px |
67
+ | lg | 52×28px | 24px | 48×48px |
68
+
69
+ Track radius: `border-radius: 9999px` (pill shape — all 8 systems agree).
70
+ Thumb travel: track width − thumb diameter − (2 × inset padding ≈ 2px).
71
+
72
+ Cross-link: `reference/surfaces.md` — concentric radius rule does NOT apply here (pill shape is intentional, not nested radius)
73
+
74
+ ---
75
+
76
+ ## Typography
77
+
78
+ - Label: 14px/400 body weight; not distinguished from surrounding text
79
+ - State text (optional): 10–11px/600 uppercase inside track — ensure ≥4.5:1 contrast against track colour
80
+
81
+ ---
82
+
83
+ ## Keyboard & Accessibility
84
+
85
+ > **WAI-ARIA role**: `switch`
86
+ > **Required attributes**: `aria-checked` ("true" / "false"); `aria-label` or visible associated label
87
+
88
+ ### Keyboard Contract
89
+
90
+ *Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/switch/ — W3C — 2024*
91
+
92
+ | Key | Action |
93
+ |-----|--------|
94
+ | Space | Toggles the switch state |
95
+ | Enter | (Optional) Toggles the switch state |
96
+
97
+ ### Accessibility Rules
98
+
99
+ - `role="switch"` with `aria-checked="true/false"` — not `role="checkbox"` (different semantic contract; switch implies immediate action)
100
+ - Label MUST be associated: `<label>` with `for` on the switch element, or `aria-label`/`aria-labelledby`
101
+ - State text ("ON"/"OFF") inside the track is a visual enhancement only — it MUST NOT be the sole accessible name
102
+ - Disabled: use `aria-disabled="true"` (not native `disabled` attr) if keyboard focus should remain (e.g. to explain why it's disabled via tooltip)
103
+ - Announce state change via `aria-live="polite"` if switching triggers a significant UI change distant from the control
104
+
105
+ ---
106
+
107
+ ## Motion
108
+
109
+ | Transition | Duration | Easing | Notes |
110
+ |------------|----------|--------|-------|
111
+ | Thumb slide | 150ms | spring (bounce: 0) | Smooth, satisfying; canonical spring values |
112
+ | Track colour | 150ms | ease | Simultaneous with thumb |
113
+ | Thumb scale on hover | 80ms | ease | 1→1.1× scale |
114
+ | Press scale | 80ms | ease | 1→0.96× (canonical press scale) |
115
+
116
+ Cross-link: `reference/motion.md` — spring bounce=0 canonical values, canonical scale-on-press 0.96
117
+
118
+ ---
119
+
120
+ ## Do / Don't
121
+
122
+ ### Do
123
+ - Use `role="switch"` not `role="checkbox"` for toggle-with-immediate-effect *(WAI-ARIA APG, Radix)*
124
+ - Animate the thumb sliding — static snap removes the "toggle" affordance *(Apple HIG, Material 3)*
125
+ - Place label to the left of the switch in LTR layouts *(Apple HIG, Material 3, Polaris)*
126
+ - Apply changes immediately on toggle — no submit button required *(Apple HIG, Polaris)*
127
+
128
+ ### Don't
129
+ - Don't use a switch inside a form where the user must click "Save" — use a checkbox *(Apple HIG, Polaris)*
130
+ - Don't rely on track colour alone to communicate state (colour blind users) — add icon or label *(Spectrum, Material 3)*
131
+ - Don't use the same `aria-label` for ON and OFF states — screen readers read the current state via `aria-checked` *(WAI-ARIA APG)*
132
+ - Don't animate thumb with `transition: all` — it catches border-radius changes and causes thumb deformation *(BAN-04)*
133
+
134
+ ---
135
+
136
+ ## Anti-patterns Cross-links
137
+
138
+ | Anti-pattern | Entry |
139
+ |--------------|-------|
140
+ | BAN-04 | `transition: all` — `reference/anti-patterns.md#ban-04` |
141
+ | Checkbox semantics for immediate-action toggle | `reference/anti-patterns.md` |
142
+
143
+ ---
144
+
145
+ ## Benchmark Citations
146
+
147
+ | Claim | Sources |
148
+ |-------|---------|
149
+ | Switch = immediate action; checkbox = form submit | Apple HIG, Material 3, Polaris |
150
+ | role="switch" not role="checkbox" | WAI-ARIA APG, Radix |
151
+ | Pill track (border-radius: 9999px) | Apple HIG, Material 3, Fluent 2 (all 8) |
152
+ | Spring motion for thumb | Material 3, Apple HIG |
153
+
154
+ Full system URLs: `connections/design-corpora.md`
155
+
156
+ ---
157
+
158
+ ## Grep Signatures
159
+
160
+ ```bash
161
+ # Switch using role="checkbox" instead of role="switch"
162
+ grep -rn 'role="checkbox"' src/ | grep -i 'switch\|toggle'
163
+
164
+ # Missing aria-checked on switch
165
+ grep -rn 'role="switch"' src/ | grep -v 'aria-checked'
166
+
167
+ # transition: all on switch thumb (BAN-04)
168
+ grep -rn 'transition:\s*all' src/ | grep -i 'switch\|thumb\|toggle'
169
+
170
+ # Switch inside <form> with submit (should be checkbox)
171
+ grep -rn 'role="switch"\|type.*switch' src/ | grep -i 'form\|submit'
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Failing Example
177
+
178
+ ```html
179
+ <!-- BAD: input[type="checkbox"] used as a switch — wrong semantic contract -->
180
+ <label>
181
+ <input type="checkbox" class="switch-toggle" />
182
+ Enable notifications
183
+ </label>
184
+ ```
185
+
186
+ **Why it fails**: `type="checkbox"` implies form-submit semantics. Screen reader announces "checkbox" not "switch". Users with mental model of "toggle = immediate effect" are confused.
187
+ **Grep detection**: `grep -rn 'class.*switch\|class.*toggle' src/ | grep 'type="checkbox"'`
188
+ **Fix**:
189
+ ```html
190
+ <button role="switch" aria-checked="false" id="notif-switch">
191
+ <span class="thumb" aria-hidden="true"></span>
192
+ </button>
193
+ <label for="notif-switch">Enable notifications</label>
194
+ ```
@@ -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,213 @@
1
+ # Tabs — Benchmark Spec
2
+
3
+ **Harvested from**: WAI-ARIA APG, Radix UI, Carbon, Mantine, Material 3, Chakra UI, Atlassian, Fluent 2
4
+ **Wave**: 2 · **Category**: Containers
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ Tabs organize content into parallel sections where only one section is visible at a time. Users navigate between tab panels without a page reload. Tabs differ from accordion (tabs are horizontal, panels mutually exclusive) and navigation (tabs do not change the URL in most implementations). The tab strip uses arrow-key navigation within the tablist, not Tab key. *(WAI-ARIA APG, Radix, Carbon all define this contract)*
11
+
12
+ ---
13
+
14
+ ## Anatomy
15
+
16
+ ```
17
+ ┌──────┬──────────┬──────┐
18
+ │ Tab1 │ Tab 2 │ Tab3 │ ← role="tablist"
19
+ └──────┴──────────┴──────┘ Each tab: role="tab" + aria-selected + aria-controls
20
+ ──────────────────────────── Panel separator (visual only)
21
+ Panel content ← role="tabpanel" + aria-labelledby
22
+
23
+ Vertical tabs (sidebar):
24
+ ┌──────────┬──────────────────┐
25
+ │ Tab 1 │ │
26
+ │ Tab 2 │ Panel content │
27
+ │ Tab 3 │ │
28
+ └──────────┴──────────────────┘
29
+ ```
30
+
31
+ | Part | Required | Notes |
32
+ |------|----------|-------|
33
+ | `role="tablist"` container | Yes | Wraps all tabs; `aria-label` or `aria-labelledby` |
34
+ | Tab triggers | Yes | `role="tab"`, `aria-selected`, `aria-controls` |
35
+ | Tab panels | Yes | `role="tabpanel"`, `aria-labelledby` (matching tab id) |
36
+ | Tab strip indicator | No | Underline or filled; shows selected tab |
37
+ | Overflow handling | Conditional | Scroll or dropdown when tabs overflow container |
38
+
39
+ ---
40
+
41
+ ## Variants
42
+
43
+ | Variant | Description | Systems |
44
+ |---------|-------------|---------|
45
+ | Default (horizontal) | Tab strip above panel | All |
46
+ | Vertical | Tab strip left of panel | Carbon, Material 3, Fluent |
47
+ | Underline | Underline indicator below selected tab | Material 3, shadcn, Atlassian |
48
+ | Filled / boxed | Selected tab has filled background | Carbon, Mantine, Fluent |
49
+ | Pill | Rounded tab shape | Mantine, Chakra |
50
+ | Scrollable | Horizontal scroll when tabs overflow | Material 3, Carbon |
51
+ | Icon + label | Icon above or beside label | Material 3, Carbon |
52
+
53
+ **Norm** (≥6/18): arrow keys navigate between tabs; Tab key moves to active panel content.
54
+ **Diverge**: automatic vs. manual activation — automatic (arrow key selects immediately) vs. manual (arrow key moves focus, Enter selects). WAI-ARIA APG recommends manual for complex panels; Radix defaults to automatic.
55
+
56
+ ---
57
+
58
+ ## States
59
+
60
+ | State | ARIA |
61
+ |-------|------|
62
+ | Selected tab | `aria-selected="true"`, `tabindex="0"` |
63
+ | Unselected tab | `aria-selected="false"`, `tabindex="-1"` |
64
+ | Focused tab | 2px focus ring; `tabindex="0"` moves to tab |
65
+ | Disabled tab | `aria-disabled="true"`, `tabindex="-1"` |
66
+ | Active panel | `role="tabpanel"`, visible, focusable |
67
+ | Inactive panel | `hidden` or `display:none` (removed from AT) |
68
+
69
+ ---
70
+
71
+ ## Sizing & Spacing
72
+
73
+ | Property | Value | Notes |
74
+ |----------|-------|-------|
75
+ | Tab height | 40–48px | Touch target compliance |
76
+ | Tab padding H | 16px | Minimum; increase for wider labels |
77
+ | Min tab width | 80px | Prevent cramped labels |
78
+ | Indicator thickness | 2–3px (underline) | On bottom edge of selected tab |
79
+ | Panel padding | 16–24px | |
80
+
81
+ ---
82
+
83
+ ## Typography
84
+
85
+ - Tab label: 14px/500 (selected), 14px/400 (unselected)
86
+ - Vertical tab label: 14px/400; left-aligned
87
+ - Tab count badge: 12px/600 in a pill; `aria-label` on tab includes count
88
+
89
+ ---
90
+
91
+ ## Keyboard & Accessibility
92
+
93
+ > **WAI-ARIA role**: `tablist` (container), `tab` (each trigger), `tabpanel` (each content panel)
94
+ > **Tab attributes**: `aria-selected`, `aria-controls` (panel id), `tabindex` (0 if selected, -1 if not)
95
+ > **Panel attributes**: `role="tabpanel"`, `aria-labelledby` (tab id), `tabindex="0"` (makes panel focusable)
96
+
97
+ ### Keyboard Contract
98
+
99
+ *Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/tabs/ — W3C — 2024*
100
+
101
+ | Key | Action |
102
+ |-----|--------|
103
+ | Tab | When focus moves into the tab list, sets focus on the active tab. When the tab list has focus, Tab moves focus to the next element in the page tab sequence (the tabpanel or element after tablist). |
104
+ | Arrow Right | Moves focus to the next tab. If focus is on the last tab, moves to the first tab. |
105
+ | Arrow Left | Moves focus to the previous tab. If focus is on the first tab, moves to the last tab. |
106
+ | Arrow Down | (Vertical tabs) Moves focus to the next tab |
107
+ | Arrow Up | (Vertical tabs) Moves focus to the previous tab |
108
+ | Space / Enter | (Manual activation only) Activates the focused tab |
109
+ | Home | Moves focus to the first tab |
110
+ | End | Moves focus to the last tab |
111
+
112
+ ### Activation Modes
113
+
114
+ - **Automatic**: arrow key moves focus AND activates the tab/panel simultaneously
115
+ - **Manual**: arrow key moves focus only; Enter/Space activates. Preferred for panels that have expensive load operations
116
+
117
+ ### Accessibility Rules
118
+
119
+ - Only the selected tab has `tabindex="0"` — all other tabs have `tabindex="-1"` (roving tabindex pattern)
120
+ - `tablist` MUST have a label: `aria-label="[Section name]"` or `aria-labelledby`
121
+ - Inactive panels MUST be hidden with `hidden` attribute (not just CSS) so AT skips them
122
+ - Panel SHOULD have `tabindex="0"` to allow focusing the panel after Tab from the tablist
123
+ - Icon-only tabs MUST have `aria-label` on the tab element
124
+ - Linked tabs (tabs that change URL): use `role="link"` semantics or native `<a>` within tab — but note this changes the keyboard contract
125
+
126
+ ---
127
+
128
+ ## Motion
129
+
130
+ | Transition | Duration | Easing | Notes |
131
+ |------------|----------|--------|-------|
132
+ | Indicator slide | 200ms | ease-out | Underline slides between tabs |
133
+ | Panel fade | 150ms | ease | Crossfade between panels |
134
+ | Scroll reveal | 200ms | ease | When scrolling to new active tab in overflow |
135
+
136
+ Cross-link: `reference/motion.md` — `prefers-reduced-motion`: disable indicator slide + panel fade
137
+
138
+ ---
139
+
140
+ ## Do / Don't
141
+
142
+ ### Do
143
+ - Use roving tabindex — `tabindex="0"` on selected, `tabindex="-1"` on all others *(WAI-ARIA APG)*
144
+ - Navigate with arrow keys between tabs, not Tab key *(WAI-ARIA APG)*
145
+ - Label the tablist with `aria-label` or `aria-labelledby` *(WAI-ARIA APG)*
146
+ - Hide inactive panels with `hidden` attribute so AT skips them *(WAI-ARIA APG)*
147
+
148
+ ### Don't
149
+ - Don't use Tab key to navigate between tabs — Tab moves in/out of the tablist *(WAI-ARIA APG)*
150
+ - Don't show all tab panel content simultaneously — defeats the purpose of tabs *(all systems)*
151
+ - Don't use more than 7 tabs in a horizontal tab strip — prefer a select or dropdown for overflow *(Carbon, Atlassian)*
152
+ - Don't use tabs for steps that must be completed in order — use a stepper *(Material 3, Atlassian)*
153
+
154
+ ---
155
+
156
+ ## Anti-patterns Cross-links
157
+
158
+ | Anti-pattern | Entry |
159
+ |--------------|-------|
160
+ | Tab navigation with Tab key (not arrow keys) | `reference/anti-patterns.md` |
161
+ | All panels visible simultaneously | `reference/anti-patterns.md` |
162
+
163
+ ---
164
+
165
+ ## Benchmark Citations
166
+
167
+ | Claim | Sources |
168
+ |-------|---------|
169
+ | Arrow keys navigate tablist | WAI-ARIA APG §3.2 |
170
+ | Tab moves in/out of tablist | WAI-ARIA APG §3.2 |
171
+ | Roving tabindex pattern | WAI-ARIA APG |
172
+ | hidden attr on inactive panels | WAI-ARIA APG |
173
+ | aria-selected="true" on active tab | WAI-ARIA APG, all systems |
174
+
175
+ Full system URLs: `connections/design-corpora.md`
176
+
177
+ ---
178
+
179
+ ## Grep Signatures
180
+
181
+ ```bash
182
+ # Tabs using Tab key navigation instead of arrow keys
183
+ grep -rn 'role="tab"' src/ | xargs grep -l 'onKeyDown.*Tab\|key.*Tab' 2>/dev/null | grep -v 'ArrowLeft\|ArrowRight'
184
+
185
+ # Tab missing aria-selected
186
+ grep -rn 'role="tab"' src/ | grep -v 'aria-selected'
187
+
188
+ # Inactive panel not hidden (just CSS)
189
+ grep -rn 'role="tabpanel"' src/ | grep -v 'hidden\|aria-hidden'
190
+
191
+ # tablist missing accessible label
192
+ grep -rn 'role="tablist"' src/ | grep -v 'aria-label\|aria-labelledby'
193
+ ```
194
+
195
+ ---
196
+
197
+ ## Failing Example
198
+
199
+ ```html
200
+ <!-- BAD: tabs using Tab key for navigation + no aria attributes -->
201
+ <div class="tab-list">
202
+ <button class="tab active">Overview</button>
203
+ <button class="tab">Details</button>
204
+ <button class="tab">Reviews</button>
205
+ </div>
206
+ <div class="tab-panel active">Overview content</div>
207
+ <div class="tab-panel">Details content</div>
208
+ <div class="tab-panel">Reviews content</div>
209
+ ```
210
+
211
+ **Why it fails**: No `role="tablist"`, `role="tab"`, `role="tabpanel"`. No `aria-selected`. No `aria-controls`/`aria-labelledby`. Tab key moves between buttons instead of arrow keys. Inactive panels are not hidden from AT.
212
+ **Grep detection**: `grep -rn 'class.*tab\b' src/ | grep -v 'role='`
213
+ **Fix**: Use Radix `<Tabs>` or implement WAI-ARIA tabs pattern with `role="tablist"`, `role="tab"` (with `aria-selected`, `aria-controls`, roving tabindex), `role="tabpanel"` (with `aria-labelledby`), and arrow-key handlers.