@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
+ # Card — Benchmark Spec
2
+
3
+ **Harvested from**: Material 3, Polaris, Carbon, Atlassian, Mantine, shadcn/ui, Ant Design, Fluent 2
4
+ **Wave**: 2 · **Category**: Containers
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ A card is a contained surface that groups related information and actions. It is a visual container, not a navigation element by default — only make the entire card clickable when the primary action is navigation and there is a single dominant action. Mixed-content cards with multiple actions should not be entirely clickable. *(Material 3, Polaris, Carbon all agree on this boundary)*
11
+
12
+ ---
13
+
14
+ ## Anatomy
15
+
16
+ ```
17
+ ┌────────────────────────────┐ ← card surface (border/shadow/background)
18
+ │ [Media / Image] │ ← optional, always with alt text
19
+ │────────────────────────────│
20
+ │ Eyebrow / Category │ ← optional; 12px/600 uppercase
21
+ │ Title │ ← primary label; ≥16px
22
+ │ Description │ ← supporting text; 14px/400
23
+ │────────────────────────────│
24
+ │ [Action 1] [Action 2] │ ← optional action area
25
+ └────────────────────────────┘
26
+ ```
27
+
28
+ | Part | Required | Notes |
29
+ |------|----------|-------|
30
+ | Container | Yes | `<article>` for standalone content; `<div>` for layout |
31
+ | Title | Yes | Descriptive; h2/h3 depending on hierarchy |
32
+ | Content | Yes | Body text, metadata, or media |
33
+ | Media / image | No | Always provide `alt`; decorative images use `alt=""` |
34
+ | Actions | No | Keep ≤2 primary actions per card |
35
+ | Footer / metadata | No | 12px/400; secondary information |
36
+
37
+ ---
38
+
39
+ ## Variants
40
+
41
+ | Variant | Description | Systems |
42
+ |---------|-------------|---------|
43
+ | Elevated | Drop shadow; floats above surface | Material 3, shadcn |
44
+ | Outlined | Border, no shadow | Material 3, Carbon, Polaris |
45
+ | Filled | Filled background, no shadow or border | Material 3, Mantine |
46
+ | Clickable / Interactive | Entire card is a link or button | Material 3, Polaris, Carbon |
47
+ | Horizontal | Media left, content right | Carbon, Polaris, Atlassian |
48
+ | Compact | Dense layout; no media | Carbon, Fluent |
49
+
50
+ **Norm** (≥5/18): outlined or elevated; ≤2 actions per card.
51
+ **Diverge**: elevation vs. outline — both are valid; use elevated for content that needs to float (dashboards), outlined for dense lists (tables of cards).
52
+
53
+ ---
54
+
55
+ ## States
56
+
57
+ | State | Trigger | Visual | ARIA/HTML |
58
+ |-------|---------|--------|-----------|
59
+ | default | — | Resting surface | — |
60
+ | hover (clickable) | pointer | Shadow deepens or border darkens | — |
61
+ | focus (clickable) | keyboard | 2px focus ring on outer card | — |
62
+ | active (clickable) | mousedown | Scale 0.99 | — |
63
+ | selected | programmatic | Border + background tint | `aria-selected="true"` |
64
+ | loading | async content | Skeleton placeholder | `aria-busy="true"` |
65
+ | disabled | programmatic | 38% opacity | `aria-disabled="true"` |
66
+
67
+ ---
68
+
69
+ ## Sizing & Spacing
70
+
71
+ | Property | Value | Notes |
72
+ |----------|-------|-------|
73
+ | Padding | 16px (sm), 20px (md), 24px (lg) | *(Carbon: 16px default, Material 3: 16px)* |
74
+ | Border radius | Use token; match `reference/surfaces.md` concentric rule | |
75
+ | Media aspect ratio | 16:9 (landscape), 1:1 (square) | `object-fit: cover` |
76
+ | Min width | 240px | Prevents content collapse |
77
+ | Max width | 480px (typical) | Grid controls actual width; max-width is a guideline |
78
+ | Gap between cards | 16px (sm grid), 24px (md grid) | |
79
+
80
+ Cross-link: `reference/surfaces.md` — concentric radius, 3-layer shadow formula, elevation tokens
81
+
82
+ ---
83
+
84
+ ## Typography
85
+
86
+ - Title: 16–20px/600 depending on card prominence (h2/h3 in DOM hierarchy)
87
+ - Eyebrow: 11px/600 uppercase, muted colour
88
+ - Body: 14px/400
89
+ - Metadata: 12px/400 muted
90
+
91
+ ---
92
+
93
+ ## Keyboard & Accessibility
94
+
95
+ > **WAI-ARIA role**: No specific card role. Use `<article>` for independent pieces of content; `<li>` in a list of cards; `<div>` for layout grouping.
96
+
97
+ ### Clickable Card Rules
98
+
99
+ *Per WAI-ARIA APG link + button patterns — W3C — 2024*
100
+
101
+ | Invocation | Element | Key |
102
+ |------------|---------|-----|
103
+ | Navigate to new page | `<a href>` wrapping or inside card | Enter |
104
+ | Trigger action in context | `<button>` wrapping or inside card | Enter, Space |
105
+
106
+ - **Do not wrap an entire card in `<a>` if it contains other interactive elements** (links, buttons inside) — nested interactive elements are inaccessible by keyboard
107
+ - Use the "card with primary action + secondary actions" pattern: one `<a>` stretched via `::after` pseudo-element to fill the card; secondary action buttons sit above in stacking context
108
+
109
+ ### Accessibility Rules
110
+
111
+ - Card with image: always provide `alt`; use `alt=""` for decorative images
112
+ - Clickable card: heading inside card should be the accessible name (via `aria-labelledby` or the stretched-link pattern)
113
+ - Card grid: use `role="list"` on the grid container and `role="listitem"` on each card, or a semantic `<ul>/<li>` structure, so screen readers announce item count
114
+ - Loading skeleton: add `aria-busy="true"` on the card container; remove when content loads
115
+
116
+ ---
117
+
118
+ ## Motion
119
+
120
+ | Transition | Duration | Easing | Notes |
121
+ |------------|----------|--------|-------|
122
+ | hover shadow | 150ms | ease-out | Elevation increase |
123
+ | press scale | 80ms | ease | 1→0.99 (subtle; card is large) |
124
+ | skeleton shimmer | 1.5s | linear loop | Respect `prefers-reduced-motion` |
125
+
126
+ Cross-link: `reference/motion.md` — Skeleton shimmer pattern, `prefers-reduced-motion`
127
+
128
+ ---
129
+
130
+ ## Do / Don't
131
+
132
+ ### Do
133
+ - Use `<article>` when the card is an independent, self-contained piece of content *(Carbon, Polaris)*
134
+ - Keep clickable cards to a single primary action; surface secondary actions as explicit buttons *(Material 3, Polaris)*
135
+ - Use the stretched-link (`::after`) pattern for clickable cards with nested links/buttons *(Carbon, Bootstrap pattern)*
136
+ - Provide `alt` text for all card images, or `alt=""` for decorative images *(WCAG 1.1.1)*
137
+
138
+ ### Don't
139
+ - Don't wrap the entire card in `<a>` if it contains other interactive elements *(WAI-ARIA APG)*
140
+ - Don't use `<div>` as a clickable card without `role="button"` or `role="link"` + keyboard handler *(WAI-ARIA APG)*
141
+ - Don't place the entire card title in a plain `<span>` when it could be `<h2>/<h3>` *(Atlassian, Carbon)*
142
+ - Don't use more than 2 primary actions per card — extract to a detail view *(Material 3, Polaris)*
143
+
144
+ ---
145
+
146
+ ## Anti-patterns Cross-links
147
+
148
+ | Anti-pattern | Entry |
149
+ |--------------|-------|
150
+ | Nested interactive elements in clickable container | `reference/anti-patterns.md` |
151
+ | Missing alt on card media | `reference/anti-patterns.md` |
152
+
153
+ ---
154
+
155
+ ## Benchmark Citations
156
+
157
+ | Claim | Sources |
158
+ |-------|---------|
159
+ | ≤2 actions per card | Material 3, Polaris, Carbon |
160
+ | Stretched-link pattern for nested interactivity | Carbon, Bootstrap |
161
+ | article element for standalone card content | Carbon, Polaris |
162
+ | 16px default padding | Carbon, Material 3 |
163
+
164
+ Full system URLs: `connections/design-corpora.md`
165
+
166
+ ---
167
+
168
+ ## Grep Signatures
169
+
170
+ ```bash
171
+ # Entire card wrapped in <a> with nested buttons/links
172
+ grep -rn '<a ' src/ | grep -i 'card' | xargs grep -l 'button\|<a ' 2>/dev/null
173
+
174
+ # Clickable div without role
175
+ grep -rn 'class.*card\|data-testid.*card' src/ | grep 'onClick\|on:click' | grep -v 'role='
176
+
177
+ # Card image without alt
178
+ grep -rn '<img' src/ | grep -i 'card' | grep -v 'alt='
179
+
180
+ # Missing heading hierarchy in card
181
+ grep -rn 'class.*card' src/ | xargs grep -L 'h[1-6]\|aria-label' 2>/dev/null
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Failing Example
187
+
188
+ ```html
189
+ <!-- BAD: entire card is <a> but contains a button — button is inaccessible by keyboard in most AT -->
190
+ <a href="/product/42" class="card">
191
+ <img src="product.jpg" />
192
+ <h3>Product Name</h3>
193
+ <p>Description…</p>
194
+ <button onclick="addToCart(42)">Add to cart</button>
195
+ </a>
196
+ ```
197
+
198
+ **Why it fails**: Nested `<button>` inside `<a>` is invalid HTML. Keyboard users pressing Tab inside the link may reach the button, but Enter on the button may also trigger the link. Screen reader behavior is unpredictable.
199
+ **Grep detection**: `grep -rn '<a.*href' src/ | xargs grep -l '<button' 2>/dev/null`
200
+ **Fix**: Use the stretched-link pattern — `<h3><a href="/product/42">Product Name</a></h3>` with `::after { position: absolute; inset: 0; }` on the `<a>`, and position the "Add to cart" button above via `position: relative; z-index: 1`.
@@ -0,0 +1,207 @@
1
+ # Checkbox — Benchmark Spec
2
+
3
+ **Harvested from**: Material 3, Carbon, Polaris, Ant Design, WAI-ARIA APG, Mantine, Chakra UI, Atlassian
4
+ **Wave**: 1 · **Category**: Inputs
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ A checkbox allows the user to select or deselect a binary option, or to represent an indeterminate state (partially selected group). Checkboxes are independent — selecting one does not affect others. Use radio buttons for mutually exclusive choices within a group. Always group related checkboxes in a `<fieldset>` with a `<legend>`.
11
+
12
+ ---
13
+
14
+ ## Anatomy
15
+
16
+ ```
17
+ ┌──┐ Label text
18
+ │✓ │ ← <input type="checkbox" id="x"> (or role="checkbox")
19
+ └──┘ Helper text (opt.)
20
+ Error message (opt.)
21
+
22
+ Group:
23
+ <fieldset>
24
+ <legend>Preferences</legend>
25
+ [checkbox] Option A
26
+ [checkbox] Option B
27
+ [checkbox] Option C (indeterminate)
28
+ </fieldset>
29
+ ```
30
+
31
+ | Part | Required | Notes |
32
+ |------|----------|-------|
33
+ | Input / control | Yes | Native `<input type="checkbox">` preferred |
34
+ | Label | Yes | `<label for="id">` — click zone includes label text |
35
+ | Fieldset + legend | Yes (group) | Required when ≥2 related checkboxes |
36
+ | Helper text | No | Below label; `aria-describedby` |
37
+ | Error message | Conditional | Field-level or group-level |
38
+ | Indeterminate indicator | Conditional | Dash/minus mark; set via `.indeterminate = true` (JS) |
39
+
40
+ ---
41
+
42
+ ## Variants
43
+
44
+ | Variant | Description | Systems |
45
+ |---------|-------------|---------|
46
+ | Default | Binary checked / unchecked | All |
47
+ | Indeterminate | Partial selection indicator (parent of group) | Material 3, Carbon, Ant, Mantine |
48
+ | Standalone | Single checkbox (e.g. "I agree to terms") | All |
49
+ | Group | ≥2 checkboxes in `<fieldset>` | All |
50
+ | With description | Label + helper text below | Material 3, Polaris, Carbon |
51
+
52
+ **Norm** (≥6/18): indeterminate state is a visual-only UI state — the underlying `checked` value is still boolean; set via DOM `.indeterminate` property, not HTML attribute.
53
+ **Diverge**: size — Material 3 uses 18px; Carbon 16px; Polaris 16px; Ant 14–16px. 16px is the de-facto norm.
54
+
55
+ ---
56
+
57
+ ## States
58
+
59
+ | State | Trigger | Visual | ARIA |
60
+ |-------|---------|--------|------|
61
+ | unchecked | default | Empty box | `aria-checked="false"` |
62
+ | checked | user interaction | Checkmark | `aria-checked="true"` |
63
+ | indeterminate | set via JS | Dash / minus | `aria-checked="mixed"` |
64
+ | hover | pointer over | Box border darkens | — |
65
+ | focus | keyboard | 2px focus ring around box | — |
66
+ | disabled unchecked | `disabled` | 38% opacity | `aria-disabled="true"` |
67
+ | disabled checked | `disabled` | 38% opacity + check | `aria-disabled="true"` |
68
+ | error | validation | Red box border | `aria-invalid="true"` |
69
+
70
+ ---
71
+
72
+ ## Sizing & Spacing
73
+
74
+ | Property | Value | Notes |
75
+ |----------|-------|-------|
76
+ | Control size | 16×16px (20px touch target via pseudo-element) | *(Carbon, Polaris, Material 3)* |
77
+ | Gap: control → label | 8px | |
78
+ | Label min click zone | Full row width | Increases tap target |
79
+ | Group item spacing | 8px vertical between items | *(Carbon, Material 3)* |
80
+ | Indentation (nested) | 24px | When showing hierarchical groups |
81
+
82
+ Cross-link: `reference/surfaces.md` — hit-area pseudo-element pattern (44×44px minimum touch target)
83
+
84
+ ---
85
+
86
+ ## Typography
87
+
88
+ - Label: 14px/400; same weight as body — checkboxes are options, not headings
89
+ - Legend: 14px/500 or 12px/600 uppercase — distinguishes group from items
90
+ - Helper/error: 12px/400
91
+
92
+ ---
93
+
94
+ ## Keyboard & Accessibility
95
+
96
+ > **WAI-ARIA role**: `checkbox` (implicit on `<input type="checkbox">`)
97
+ > **Required attributes**: `aria-checked` (if not native); `aria-describedby` for helper/error
98
+
99
+ ### Keyboard Contract
100
+
101
+ *Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/checkbox/ — W3C — 2024*
102
+
103
+ | Key | Action |
104
+ |-----|--------|
105
+ | Tab | Moves focus to the checkbox |
106
+ | Space | Toggles the checkbox state (checked / unchecked / indeterminate) |
107
+
108
+ Within a group: Tab moves between checkboxes (not arrow keys — checkboxes are independent).
109
+
110
+ ### Accessibility Rules
111
+
112
+ - Label MUST be associated via `<label for="id">` — clicking the label must toggle the checkbox
113
+ - `<fieldset>` + `<legend>` MUST wrap every group of related checkboxes — the legend provides group context to screen readers
114
+ - Indeterminate state MUST be set via JS `.indeterminate = true` — there is no HTML attribute; `aria-checked="mixed"` must be set simultaneously on `role="checkbox"` elements
115
+ - Disabled checkboxes: use native `disabled` attribute for form semantics; `aria-disabled="true"` if the element must remain in tab order (e.g. with explanatory tooltip)
116
+ - Error state: `aria-invalid="true"` on the control; group-level error on the `<fieldset>` via `aria-describedby`
117
+
118
+ ---
119
+
120
+ ## Motion
121
+
122
+ | Transition | Duration | Easing | Notes |
123
+ |------------|----------|--------|-------|
124
+ | check fill | 120ms | ease-out | SVG path draw or scale from center |
125
+ | indeterminate dash | 120ms | ease | Width animation of dash element |
126
+ | hover border | 80ms | ease | Border colour only |
127
+
128
+ Cross-link: `reference/motion.md` — `prefers-reduced-motion`: skip path animation, show fill instantly
129
+
130
+ ---
131
+
132
+ ## Do / Don't
133
+
134
+ ### Do
135
+ - Use `<fieldset>` + `<legend>` for every group *(WAI-ARIA APG, Carbon, Polaris)*
136
+ - Set indeterminate via `.indeterminate = true` AND `aria-checked="mixed"` *(WAI-ARIA APG, Mantine)*
137
+ - Make the entire label row clickable, not just the box *(Material 3, Carbon, Polaris)*
138
+ - Align label text to the top of the control in multiline label scenarios *(Carbon)*
139
+
140
+ ### Don't
141
+ - Don't use checkboxes for mutually exclusive options — use radio buttons *(Material 3, Carbon, Polaris)*
142
+ - Don't use a custom `<div>` checkbox without `role="checkbox"` and keyboard handler *(WAI-ARIA APG)*
143
+ - Don't set `aria-checked="mixed"` via HTML attribute — it must be set dynamically *(WAI-ARIA APG)*
144
+ - Don't rely on colour alone for checked state — always include a visible checkmark *(WCAG 1.4.1)*
145
+
146
+ ---
147
+
148
+ ## Anti-patterns Cross-links
149
+
150
+ | Anti-pattern | Entry |
151
+ |--------------|-------|
152
+ | Custom checkbox without role | `reference/anti-patterns.md` |
153
+
154
+ ---
155
+
156
+ ## Benchmark Citations
157
+
158
+ | Claim | Sources |
159
+ |-------|---------|
160
+ | Space toggles checkbox | WAI-ARIA APG §3.5 |
161
+ | fieldset+legend required for group | WAI-ARIA APG, Carbon, Polaris |
162
+ | .indeterminate = true (JS only) | WAI-ARIA APG, MDN |
163
+ | 16px control size | Carbon, Polaris, Material 3 |
164
+
165
+ Full system URLs: `connections/design-corpora.md`
166
+
167
+ ---
168
+
169
+ ## Grep Signatures
170
+
171
+ ```bash
172
+ # Custom checkbox div/span without role="checkbox"
173
+ grep -rn 'class.*checkbox\|type.*checkbox' src/ | grep '<div\|<span' | grep -v 'role='
174
+
175
+ # Missing label association
176
+ grep -rn 'type="checkbox"' src/ | grep -v 'id=\|aria-label'
177
+
178
+ # Group without fieldset
179
+ grep -rn 'checkbox' src/ | grep -v 'fieldset\|role="group"'
180
+
181
+ # Indeterminate set via attribute instead of JS
182
+ grep -rn 'indeterminate' src/ | grep 'setAttribute\|attr(' | grep '"true"'
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Failing Example
188
+
189
+ ```html
190
+ <!-- BAD: checkboxes in a group without fieldset/legend — group context lost for screen readers -->
191
+ <div>
192
+ <p>Notification preferences</p>
193
+ <input type="checkbox" id="email"> <label for="email">Email</label>
194
+ <input type="checkbox" id="sms"> <label for="sms">SMS</label>
195
+ </div>
196
+ ```
197
+
198
+ **Why it fails**: Screen readers announce each option without the group context ("Email — checkbox") — users don't know what these options belong to without reading surrounding text.
199
+ **Grep detection**: `grep -B5 'type="checkbox"' src/ | grep -v 'fieldset\|role="group"'`
200
+ **Fix**:
201
+ ```html
202
+ <fieldset>
203
+ <legend>Notification preferences</legend>
204
+ <input type="checkbox" id="email"> <label for="email">Email</label>
205
+ <input type="checkbox" id="sms"> <label for="sms">SMS</label>
206
+ </fieldset>
207
+ ```
@@ -0,0 +1,209 @@
1
+ # Chip / Tag — Benchmark Spec
2
+
3
+ **Harvested from**: Material 3, Carbon, Atlassian, Mantine
4
+ **Wave**: 3 · **Category**: Feedback
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ A chip (or tag) is a compact, interactive label that represents an attribute, filter, or selection. It is commonly used for multi-select filter interfaces, tag-input fields (user-generated labels), one-time suggestion prompts, and static display labels. Each variant has distinct interaction semantics — filter chips toggle on/off, input chips are removable, suggestion chips trigger a one-time action, and display chips are static. *(Material 3, Carbon, Atlassian, Mantine agree on the four-variant taxonomy)*
11
+
12
+ *UUPM app-interface: filter chips in search/filter UIs and tag input patterns (MIT attribution)*
13
+
14
+ ---
15
+
16
+ ## Anatomy
17
+
18
+ ```
19
+ ┌───────────────────────────────┐
20
+ │ [icon?] Label text [✕ remove?] │
21
+ └───────────────────────────────┘
22
+ ↑ role varies by variant
23
+ ↑ touch target ≥ 32px height
24
+ ```
25
+
26
+ | Part | Required | Notes |
27
+ |------|----------|-------|
28
+ | Label text | Yes | Concise (1–3 words typical) |
29
+ | Container | Yes | Interactive element; role/type depends on variant |
30
+ | Leading icon | No | 16px; left-aligned; 8px gap to label |
31
+ | Remove button | Conditional | Required on input/tag chips; independent focusable element |
32
+ | Selected state indicator | Conditional | Checkmark or filled style on toggled filter chips |
33
+
34
+ ---
35
+
36
+ ## Variants
37
+
38
+ | Variant | Interaction | Role | Systems |
39
+ |---------|-------------|------|---------|
40
+ | Filter | Toggleable; multi-select | `aria-pressed` or `role="option"` in `role="listbox"` | Material 3, Carbon, Atlassian, Mantine |
41
+ | Input / Tag | User-generated; removable via × button | `role="option"` in combobox; or `role="listbox"` child | Material 3, Atlassian, Mantine |
42
+ | Suggestion | One-time select; fires an action then typically disappears | `role="button"` | Material 3, Mantine |
43
+ | Display | Static label; no interaction | No role needed (or `role="listitem"`) | Atlassian (Lozenge), Carbon (Tag), Mantine |
44
+
45
+ **Norm** (≥4/18 systems agree): filter chip uses `aria-pressed` for standalone toggles; input chip requires independent remove button with its own `aria-label`; display chip is decorative/static.
46
+ **Diverge**: Material 3 calls static chips "Suggestion chips" when one-time-action and "Assist chips" for shortcut actions; Atlassian separates "Tag" (removable) from "Lozenge" (static status label); Carbon calls them "Tag" uniformly with a `filter` prop.
47
+
48
+ ---
49
+
50
+ ## States
51
+
52
+ | State | Trigger | Visual | ARIA |
53
+ |-------|---------|--------|------|
54
+ | default | — | Rest fill + label | — |
55
+ | hover | pointer over chip | Background tint (+8%) | — |
56
+ | focus | keyboard focus on chip | focus-visible ring (2px) | — |
57
+ | selected (filter) | click / Enter / Space | Filled background; checkmark icon | `aria-pressed="true"` |
58
+ | unselected (filter) | click / Enter / Space | Outline style | `aria-pressed="false"` |
59
+ | disabled | `disabled` / `aria-disabled` | 38% opacity; cursor not-allowed | `aria-disabled="true"` |
60
+ | remove focus | Tab to × button | Focus ring on × | — |
61
+
62
+ ---
63
+
64
+ ## Sizing & Spacing
65
+
66
+ | Property | Value | Notes |
67
+ |----------|-------|-------|
68
+ | Height | 32px (min touch target) | Do not reduce below 32px |
69
+ | Padding-x | 12px | Each side; 8px when leading icon present |
70
+ | Gap (icon → label) | 8px | |
71
+ | Gap (label → remove ×) | 4px | |
72
+ | Remove button size | 20×20px (visual); 32×32px (touch) | Extend touch area with padding |
73
+ | Border radius | 16–20px (full pill) | *(Material 3: full-radius pill; Carbon: 4px; Atlassian: 2px)* |
74
+ | Font size | 13–14px / 400 | |
75
+
76
+ **Norm**: pill shape (full-radius) for filter and input chips *(Material 3, Mantine)*; Touch target ≥ 32px height *(WCAG 2.5.5, Material 3)*.
77
+
78
+ ---
79
+
80
+ ## Typography
81
+
82
+ - Label: body-sm (13–14px/400) — same scale as form labels
83
+ - No bold weight — chip label is a tag, not a heading or CTA
84
+ - Truncate with ellipsis only when chip is in a fixed-width container; prefer wrapping chip set to natural width
85
+
86
+ Cross-link: `reference/typography.md` — body-sm definition
87
+
88
+ ---
89
+
90
+ ## Keyboard & Accessibility
91
+
92
+ > **WAI-ARIA role**: `button` (filter standalone, suggestion); `option` inside `listbox` (filter group, input chips)
93
+ > **Required attributes**: `aria-pressed` on standalone filter chip; `aria-label` on remove button; `aria-selected` if `role="option"`
94
+
95
+ ### Keyboard Contract
96
+
97
+ *Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/listbox/ — W3C — 2024*
98
+
99
+ | Key | Action |
100
+ |-----|--------|
101
+ | Tab | Moves focus to the chip or to the remove (×) button |
102
+ | Enter / Space | Toggles filter chip (pressed/unpressed); activates suggestion chip |
103
+ | Delete / Backspace | Removes an input/tag chip (when chip or its remove button has focus) |
104
+ | Arrow Left / Right | Navigates between chips when inside a `role="listbox"` group |
105
+ | Escape | Collapses chip group if it was expanded (e.g. overflow +N pattern) |
106
+
107
+ ### Accessibility Rules
108
+
109
+ - Filter chip used as standalone toggle MUST have `aria-pressed="true|false"` — absence means screen readers cannot report toggle state *(WAI-ARIA APG)*
110
+ - Filter chips inside a multi-select group SHOULD use `role="option"` inside `role="listbox"` with `aria-multiselectable="true"` *(WAI-ARIA APG)*
111
+ - Remove button MUST have an independent `aria-label` describing what it removes: `aria-label="Remove Python tag"` — the × glyph alone is not an accessible name *(WAI-ARIA APG, Material 3)*
112
+ - Remove button MUST be a separate focusable element, NOT a click handler on the chip label *(Carbon, Atlassian)*
113
+ - Touch target for remove button MUST be ≥ 32×32px via padding even if the visual × is 16–20px *(WCAG 2.5.5)*
114
+ - Display chips have no role — they are presentational; if they carry meaningful status, use `role="status"` or integrate into an Alert *(Atlassian Lozenge guidance)*
115
+
116
+ Cross-link: `reference/accessibility.md` — `aria-pressed`, `listbox`, touch targets
117
+
118
+ ---
119
+
120
+ ## Motion
121
+
122
+ | Transition | Duration | Easing | Notes |
123
+ |------------|----------|--------|-------|
124
+ | Toggle selected (background fill) | 100ms | ease-out | Background + checkmark appear |
125
+ | Chip remove (collapse width) | 150ms | ease-in | Width collapses to 0, siblings shift |
126
+ | Chip add (expand width) | 150ms | ease-out | Width expands from 0 |
127
+ | Hover background | 80ms | ease-out | Subtle tint only |
128
+
129
+ **BAN**: Full-page reflow animation when removing a chip — collapse width inline; do not shift unrelated page sections. Do not use `transition: all` (catches unintended border/shadow changes).
130
+
131
+ Cross-link: `reference/motion.md` — `prefers-reduced-motion`: skip width animation, instant add/remove
132
+
133
+ ---
134
+
135
+ ## Do / Don't
136
+
137
+ ### Do
138
+ - Add `aria-pressed` to standalone filter chips *(WAI-ARIA APG)*
139
+ - Give remove buttons an independent `aria-label`: "Remove [label] tag" *(WAI-ARIA APG, Material 3)*
140
+ - Use `role="listbox"` + `role="option"` for multi-select filter chip groups *(WAI-ARIA APG)*
141
+ - Ensure touch target ≥ 32px height; extend remove button via padding *(WCAG 2.5.5)*
142
+
143
+ ### Don't
144
+ - Don't put filter chips without `aria-pressed` — screen readers cannot report selected state *(WAI-ARIA APG)*
145
+ - Don't use the same click handler for chip label and remove — remove must be independently focusable *(Carbon, Atlassian)*
146
+ - Don't truncate chip label in narrow containers without a tooltip — truncated tags lose meaning *(Polaris, Mantine)*
147
+ - Don't use display chips for dynamic statuses that change — use Alert or Badge instead *(Atlassian)*
148
+
149
+ ---
150
+
151
+ ## Anti-patterns Cross-links
152
+
153
+ | Anti-pattern | Entry |
154
+ |--------------|-------|
155
+ | Filter chip without aria-pressed | `reference/anti-patterns.md#ban-aria-pressed` |
156
+ | Remove button without aria-label | `reference/anti-patterns.md#ban-aria-label` |
157
+
158
+ ---
159
+
160
+ ## Benchmark Citations
161
+
162
+ | Claim | Sources |
163
+ |-------|---------|
164
+ | aria-pressed on standalone filter chip | WAI-ARIA APG, Material 3, Carbon |
165
+ | Remove button independent aria-label | WAI-ARIA APG, Material 3, Atlassian |
166
+ | role="option" inside role="listbox" for group | WAI-ARIA APG |
167
+ | Touch target ≥ 32px height | WCAG 2.5.5, Material 3 |
168
+ | Delete/Backspace removes input chip | Material 3, Atlassian, Mantine |
169
+ | UUPM filter/tag-input patterns | UUPM app-interface (MIT) |
170
+
171
+ Full system URLs: `connections/design-corpora.md`
172
+
173
+ ---
174
+
175
+ ## Grep Signatures
176
+
177
+ ```bash
178
+ # Filter chip missing aria-pressed
179
+ grep -rn 'chip\|Chip\|filter-tag\|filterChip' src/ | grep -i 'filter\|toggle' | grep -v 'aria-pressed'
180
+
181
+ # Remove button on chip missing aria-label
182
+ grep -rn 'chip.*remove\|tag.*remove\|remove.*chip\|remove.*tag' src/ | grep -v 'aria-label'
183
+
184
+ # Chip remove button without independent tab stop (not a separate button element)
185
+ grep -rn 'chip\|tag' src/ | grep '×\|&times;\|✕\|close' | grep -v '<button\|role="button"'
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Failing Example
191
+
192
+ ```html
193
+ <!-- BAD: filter chip with no aria-pressed — screen readers cannot report toggle state -->
194
+ <div class="chip chip--filter" onclick="toggleFilter(this)">
195
+ Python
196
+ </div>
197
+ ```
198
+
199
+ **Why it fails**: `<div>` is not keyboard reachable. No `aria-pressed` means screen readers cannot determine if the filter is active or inactive. No `role="button"` or `tabindex` so keyboard users skip it entirely. No focus-visible ring.
200
+ **Grep detection**: `grep -rn 'chip--filter\|filterChip\|chip.*filter' src/ | grep -v 'aria-pressed'`
201
+ **Fix**:
202
+ ```html
203
+ <button class="chip chip--filter"
204
+ aria-pressed="false"
205
+ onclick="toggleFilter(this)">
206
+ Python
207
+ </button>
208
+ ```
209
+ Update `aria-pressed` to `"true"` when selected.