@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,198 @@
|
|
|
1
|
+
# Breadcrumbs — Benchmark Spec
|
|
2
|
+
|
|
3
|
+
**Harvested from**: WAI-ARIA APG, Carbon, Polaris, Material 3, Atlassian
|
|
4
|
+
**Wave**: 4 · **Category**: Navigation
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
A breadcrumb trail shows a user's location within a hierarchical navigation structure, providing a supplemental path back to any ancestor page. It is not primary navigation — the current page is always the last item and is never a link. Breadcrumbs are most valuable in deep hierarchies (> 2 levels) and information-dense applications. *(WAI-ARIA APG breadcrumb pattern, Carbon, Polaris, Material 3, Atlassian all define breadcrumb as a supplemental location indicator)*
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Anatomy
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
<nav aria-label="Breadcrumb">
|
|
18
|
+
<ol role="list">
|
|
19
|
+
<li><a href="/">Home</a></li>
|
|
20
|
+
<li><span aria-hidden="true">›</span><a href="/products">Products</a></li>
|
|
21
|
+
<li><span aria-hidden="true">›</span><span aria-current="page">Widget Pro</span></li>
|
|
22
|
+
</ol>
|
|
23
|
+
</nav>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Part | Required | Notes |
|
|
27
|
+
|------|----------|-------|
|
|
28
|
+
| `<nav>` container | Yes | `role="navigation"` + `aria-label="Breadcrumb"` |
|
|
29
|
+
| `<ol>` list | Yes | Ordered list — sequence matters |
|
|
30
|
+
| `<li>` items | Yes | One per level in the path |
|
|
31
|
+
| Ancestor links | Yes | `<a href>` pointing to each ancestor URL |
|
|
32
|
+
| Current page | Yes | Plain text `<span>` (not a link) + `aria-current="page"` |
|
|
33
|
+
| Separator | Yes | `aria-hidden="true"` character or SVG icon; never in link text |
|
|
34
|
+
| Ellipsis (deep paths) | No | Truncate middle items at > 4 levels; always keep first + last 2 |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Variants
|
|
39
|
+
|
|
40
|
+
| Variant | Description | Systems |
|
|
41
|
+
|---------|-------------|---------|
|
|
42
|
+
| Default | Inline trail, all items visible | All systems |
|
|
43
|
+
| Collapsed / truncated | Middle items replaced by "…" at > 4 levels | Carbon, Polaris, Atlassian |
|
|
44
|
+
| With icons | Leading icon per item (folder/page icon) | Material 3, Atlassian |
|
|
45
|
+
| Large / heading-inline | Larger type, sits beside or below a page title | Polaris, Atlassian |
|
|
46
|
+
|
|
47
|
+
**Norm** (≥4 systems agree): separator is `aria-hidden`; current page uses `aria-current="page"`; last item is never a link.
|
|
48
|
+
**Diverge**: Polaris uses `>` as separator; Carbon uses `/`; Material 3 uses `›` (chevron). All are acceptable — choose to match your product's visual language.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## States
|
|
53
|
+
|
|
54
|
+
| State | Trigger | Visual | ARIA |
|
|
55
|
+
|-------|---------|--------|------|
|
|
56
|
+
| default | — | Ancestor links + muted separator + current page text | — |
|
|
57
|
+
| link-hover | pointer over ancestor | Underline or color shift | — |
|
|
58
|
+
| link-focus | keyboard focus on ancestor | 2px focus-visible ring | — |
|
|
59
|
+
| current | — | No underline; muted or bold treatment; not clickable | `aria-current="page"` |
|
|
60
|
+
| truncated | path > 4 levels | Middle items hidden; "…" button shown | `aria-label="Show full path"` on ellipsis button |
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Sizing & Spacing
|
|
65
|
+
|
|
66
|
+
| Element | Value | Notes |
|
|
67
|
+
|---------|-------|-------|
|
|
68
|
+
| Item font size | 13–14px (body-sm) | Smaller than page headings; supplemental context |
|
|
69
|
+
| Item height | 24–32px | Allow for comfortable touch targets on mobile |
|
|
70
|
+
| Separator spacing | 4–8px H padding each side | Optical breathing room |
|
|
71
|
+
| Max visible levels | 4 | Truncate middle items beyond 4 levels |
|
|
72
|
+
| Truncation rule | Keep: first + last 2 items | Never truncate the current page or the root |
|
|
73
|
+
|
|
74
|
+
**Norm**: 13–14px font size (Carbon, Polaris, Atlassian); items on a single line; no wrapping.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Typography
|
|
79
|
+
|
|
80
|
+
- Ancestor links: body-sm, weight 400, `color: --text-link`; underline on hover
|
|
81
|
+
- Current page: body-sm, weight 500 or 600 (bold for emphasis), `color: --text-primary`; no underline
|
|
82
|
+
- Separator: body-sm, `color: --text-subtle`, `aria-hidden="true"`
|
|
83
|
+
- Do not use uppercase or letter-spacing on breadcrumb items — they should read as natural path segments
|
|
84
|
+
|
|
85
|
+
Cross-link: `reference/typography.md` — body-sm, link color tokens
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Keyboard & Accessibility
|
|
90
|
+
|
|
91
|
+
> **WAI-ARIA role**: `navigation` (container)
|
|
92
|
+
> **Required attributes**: `aria-label="Breadcrumb"` on `<nav>`; `aria-current="page"` on last item; `aria-hidden="true"` on separators
|
|
93
|
+
|
|
94
|
+
### Keyboard Contract
|
|
95
|
+
|
|
96
|
+
*Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/ — W3C — 2024*
|
|
97
|
+
|
|
98
|
+
| Key | Action |
|
|
99
|
+
|-----|--------|
|
|
100
|
+
| Tab | Moves focus to next focusable link in the breadcrumb trail |
|
|
101
|
+
| Shift+Tab | Moves focus to previous link |
|
|
102
|
+
| Enter | Activates the focused link (navigates to ancestor) |
|
|
103
|
+
|
|
104
|
+
### Accessibility Rules
|
|
105
|
+
|
|
106
|
+
- `aria-current="page"` MUST be on the last item (current page) — this is the primary AT signal
|
|
107
|
+
- Separators MUST be `aria-hidden="true"` — screen readers should announce "Home, Products, Widget Pro" not "Home › Products › Widget Pro"
|
|
108
|
+
- The current page item MUST NOT be a link — it is the user's current location
|
|
109
|
+
- `<ol>` conveys that order matters; do not use `<ul>` or a `<div>` list
|
|
110
|
+
- `aria-label="Breadcrumb"` distinguishes this nav from primary/secondary navigation landmarks
|
|
111
|
+
|
|
112
|
+
Cross-link: `reference/accessibility.md` — aria-current, landmark labelling
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Motion
|
|
117
|
+
|
|
118
|
+
| Transition | Duration | Easing | Notes |
|
|
119
|
+
|------------|----------|--------|-------|
|
|
120
|
+
| Ellipsis expand | 150ms | ease-out | Reveal hidden items inline |
|
|
121
|
+
| Ellipsis collapse | 120ms | ease-in | Hide middle items |
|
|
122
|
+
|
|
123
|
+
**BAN**: Do not animate the breadcrumb trail on route change — route transitions are the page's responsibility, not the breadcrumb's.
|
|
124
|
+
|
|
125
|
+
Cross-link: `reference/motion.md` — layout transitions
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Do / Don't
|
|
130
|
+
|
|
131
|
+
### Do
|
|
132
|
+
- Use `<ol>` for the list — order is meaningful in a hierarchical path *(WAI-ARIA APG)*
|
|
133
|
+
- Mark the current page with `aria-current="page"` — not just a CSS class *(WAI-ARIA APG, Atlassian)*
|
|
134
|
+
- Hide separators with `aria-hidden="true"` *(WAI-ARIA APG, Carbon, Polaris)*
|
|
135
|
+
- Truncate middle items (not first/last) when path exceeds 4 levels *(Carbon, Polaris, Atlassian)*
|
|
136
|
+
|
|
137
|
+
### Don't
|
|
138
|
+
- Don't make the current page item a link — it creates a circular self-referential link *(WAI-ARIA APG)*
|
|
139
|
+
- Don't include separator text inside link labels — `aria-hidden` the separator element, not the link *(WAI-ARIA APG)*
|
|
140
|
+
- Don't use breadcrumbs as the only navigation method — they supplement; primary nav is required *(Material 3, Polaris)*
|
|
141
|
+
- Don't truncate the root or current page items — users need anchoring context at both ends *(Carbon, Atlassian)*
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Anti-patterns Cross-links
|
|
146
|
+
|
|
147
|
+
| Anti-pattern | Entry |
|
|
148
|
+
|--------------|-------|
|
|
149
|
+
| BAN-07 | Missing `aria-current` on active navigation items — `reference/anti-patterns.md#ban-07` |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Benchmark Citations
|
|
154
|
+
|
|
155
|
+
| Claim | Sources |
|
|
156
|
+
|-------|---------|
|
|
157
|
+
| aria-label="Breadcrumb" on nav container | WAI-ARIA APG breadcrumb pattern |
|
|
158
|
+
| Separator must be aria-hidden | WAI-ARIA APG, Carbon, Polaris, Atlassian |
|
|
159
|
+
| Last item not a link + aria-current="page" | WAI-ARIA APG, Carbon, Polaris, Material 3 |
|
|
160
|
+
| Ordered list (`<ol>`) for hierarchy | WAI-ARIA APG |
|
|
161
|
+
| Truncate middle at > 4 levels | Carbon, Polaris, Atlassian |
|
|
162
|
+
|
|
163
|
+
Full system URLs: `connections/design-corpora.md`
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Grep Signatures
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Last breadcrumb item is a link (should be plain text with aria-current)
|
|
171
|
+
grep -rn 'breadcrumb\|bread-crumb' src/ | grep -v 'aria-current="page"'
|
|
172
|
+
|
|
173
|
+
# Separator not aria-hidden
|
|
174
|
+
grep -rn 'breadcrumb' src/ | grep '›\|/\|>\|chevron' | grep -v 'aria-hidden'
|
|
175
|
+
|
|
176
|
+
# Using <ul> instead of <ol> for breadcrumb
|
|
177
|
+
grep -rn 'breadcrumb' src/ | grep '<ul'
|
|
178
|
+
|
|
179
|
+
# nav container missing aria-label
|
|
180
|
+
grep -rn 'breadcrumb' src/ | grep '<nav' | grep -v 'aria-label'
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Failing Example
|
|
186
|
+
|
|
187
|
+
```html
|
|
188
|
+
<!-- BAD: all items are links including the current page, separators not hidden -->
|
|
189
|
+
<nav> <!-- missing aria-label="Breadcrumb" -->
|
|
190
|
+
<a href="/">Home</a> /
|
|
191
|
+
<a href="/products">Products</a> /
|
|
192
|
+
<a href="/products/widget-pro">Widget Pro</a> <!-- should NOT be a link; no aria-current -->
|
|
193
|
+
</nav>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Why it fails**: Current page is a link (circular/confusing); separators `/` are announced by screen readers; `<nav>` has no label; no `aria-current="page"` to signal location to AT users; no `<ol>/<li>` structure loses hierarchy semantics.
|
|
197
|
+
**Grep detection**: `grep -rn 'breadcrumb' src/ | grep -v 'aria-current'`
|
|
198
|
+
**Fix**: Replace last `<a>` with `<span aria-current="page">Widget Pro</span>`; wrap separators in `<span aria-hidden="true">/</span>`; add `aria-label="Breadcrumb"` to `<nav>`; wrap items in `<ol><li>`.
|
|
@@ -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 '×\|×\|✕\|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.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Command Palette — Benchmark Spec
|
|
2
|
+
|
|
3
|
+
**Harvested from**: Linear, Raycast, Radix CMDK, GitHub Primer, UUPM (app-interface, MIT)
|
|
4
|
+
**Wave**: 4 · **Category**: Navigation & Data
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
A command palette is a keyboard-first global launcher that lets users search across commands, pages, and actions from anywhere in the application without navigating menus. Triggered by Cmd+K (macOS) / Ctrl+K (Windows/Linux), it provides fast access to frequently used actions and deep navigation. It is distinct from a local search input (scoped to one page) and from a Menu (contextual, anchored). *(Linear command palette, Raycast, Radix CMDK, GitHub Primer all converge on Cmd/Ctrl+K trigger + dialog + combobox + listbox pattern)*
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Anatomy
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
┌─────────────────────────────────────────────────┐
|
|
18
|
+
│ role="dialog" aria-modal="true" │
|
|
19
|
+
│ aria-label="Command palette" │
|
|
20
|
+
│ ┌─────────────────────────────────────────────┐ │
|
|
21
|
+
│ │ 🔍 [Search commands… ] │ │ role="combobox"
|
|
22
|
+
│ └─────────────────────────────────────────────┘ │ aria-autocomplete="list"
|
|
23
|
+
│ ┌─────────────────────────────────────────────┐ │
|
|
24
|
+
│ │ role="listbox" id="cmd-results" │ │
|
|
25
|
+
│ │ ── Recent ────────── role="group" │ │
|
|
26
|
+
│ │ Dashboard role="option" │ │
|
|
27
|
+
│ │ ── Commands ────────── role="group" │ │
|
|
28
|
+
│ │ ▶ New Project role="option" ● │ │ aria-selected="true"
|
|
29
|
+
│ │ Invite member role="option" │ │
|
|
30
|
+
│ └─────────────────────────────────────────────┘ │
|
|
31
|
+
└─────────────────────────────────────────────────┘
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| Part | Required | Notes |
|
|
35
|
+
|------|----------|-------|
|
|
36
|
+
| Dialog overlay | Yes | `role="dialog"` + `aria-modal="true"` + `aria-label="Command palette"` |
|
|
37
|
+
| Focus trap | Yes | Tab/Shift+Tab cycle within dialog only |
|
|
38
|
+
| Search input | Yes | `role="combobox"` + `aria-expanded` + `aria-autocomplete="list"` + `aria-controls` |
|
|
39
|
+
| Results list | Yes | `role="listbox"` + `id` (target of `aria-controls`) |
|
|
40
|
+
| Result items | Yes | `role="option"` + `aria-selected` |
|
|
41
|
+
| Section groups | No | `role="group"` + `aria-label` per section (Recent, Commands, Pages) |
|
|
42
|
+
| Empty state | Yes | "No results for X" text; `aria-live="polite"` on result region |
|
|
43
|
+
| Keyboard shortcut hints | No | `aria-hidden="true"`; display-only |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Variants
|
|
48
|
+
|
|
49
|
+
| Variant | Description | Systems |
|
|
50
|
+
|---------|-------------|---------|
|
|
51
|
+
| Default | Cmd/Ctrl+K global trigger; full-width results | Linear, Raycast, Radix CMDK |
|
|
52
|
+
| Inline search | Embedded within a page; narrower context | Primer, Atlassian |
|
|
53
|
+
| Global search | Cross-section nav + recent pages + action shortcuts | UUPM app-interface (MIT) |
|
|
54
|
+
| With categories | Grouped results by type (Recent / Commands / Pages) | Linear, Raycast, UUPM |
|
|
55
|
+
| With icons | Category and action icons beside results | Linear, Raycast |
|
|
56
|
+
| Nested commands | Select a command to reveal sub-commands | Raycast, Radix CMDK |
|
|
57
|
+
|
|
58
|
+
**Norm** (≥4 systems agree): `role="dialog"` + focus trap + `role="combobox"` input + `role="listbox"` results.
|
|
59
|
+
**Diverge**: Raycast supports nested command flows (select command → enter parameters); Linear/CMDK keep it flat for speed.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## States
|
|
64
|
+
|
|
65
|
+
| State | Trigger | Visual | ARIA |
|
|
66
|
+
|-------|---------|--------|------|
|
|
67
|
+
| closed | — | Dialog hidden | — |
|
|
68
|
+
| open | Cmd/Ctrl+K | Dialog visible; input focused | `aria-expanded="true"` on combobox |
|
|
69
|
+
| empty | no query | Placeholder or recent items | `aria-expanded="true"` |
|
|
70
|
+
| searching | typing | Results update in real-time | `aria-live="polite"` on results region |
|
|
71
|
+
| result-focus | ArrowDown/Up | Item highlighted | `aria-selected="true"` on option |
|
|
72
|
+
| no-results | no matches | "No results for X" message | `aria-live="polite"` announces update |
|
|
73
|
+
| loading | async search | Spinner in input or results | `aria-busy="true"` on listbox |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Sizing & Spacing
|
|
78
|
+
|
|
79
|
+
| Element | Value | Notes |
|
|
80
|
+
|---------|-------|-------|
|
|
81
|
+
| Dialog width | 560–640px | Centered horizontally |
|
|
82
|
+
| Dialog max-height | 480px | Results region scrolls beyond this |
|
|
83
|
+
| Input height | 48–56px | Generous; primary interaction surface |
|
|
84
|
+
| Result item height | 40px | Icon + label + shortcut hint |
|
|
85
|
+
| Section label height | 28px | Muted heading; non-interactive |
|
|
86
|
+
| Backdrop | 40–60% opacity black | `rgba(0,0,0,0.5)`; click to dismiss |
|
|
87
|
+
|
|
88
|
+
**Norm**: 560–640px width (Linear, Raycast, CMDK all converge). Input height 48px+ for legibility.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Typography
|
|
93
|
+
|
|
94
|
+
- Input placeholder: body-md (15–16px), `color: --text-placeholder`
|
|
95
|
+
- Input value: body-md (15–16px), weight 400
|
|
96
|
+
- Result item label: body-sm (13–14px), weight 400; matched substring bold/highlighted
|
|
97
|
+
- Section heading: label-xs (11px), uppercase, weight 600, `color: --text-subtle`, `aria-hidden="true"` if role="group" handles it
|
|
98
|
+
- Keyboard shortcut hints: label-xs (11px), `color: --text-subtle`, right-aligned, `aria-hidden="true"`
|
|
99
|
+
|
|
100
|
+
Cross-link: `reference/typography.md` — body-md, label scale
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Keyboard & Accessibility
|
|
105
|
+
|
|
106
|
+
> **WAI-ARIA role**: `dialog` (overlay), `combobox` (input), `listbox` (results), `option` (items), `group` (sections)
|
|
107
|
+
> **Required attributes**: `aria-modal="true"` on dialog; `aria-label="Command palette"` on dialog; `aria-expanded` + `aria-autocomplete="list"` + `aria-controls` on combobox; `aria-selected` on options; `aria-label` on each `role="group"`
|
|
108
|
+
|
|
109
|
+
### Keyboard Contract
|
|
110
|
+
|
|
111
|
+
*Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/combobox/ and https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/ — W3C — 2024*
|
|
112
|
+
|
|
113
|
+
| Key | Action |
|
|
114
|
+
|-----|--------|
|
|
115
|
+
| Cmd+K / Ctrl+K | Opens the command palette (global shortcut) |
|
|
116
|
+
| ArrowDown | Moves focus to first result (from input) or next result |
|
|
117
|
+
| ArrowUp | Moves focus to previous result; loops to input |
|
|
118
|
+
| Home | Moves focus to first result in list |
|
|
119
|
+
| End | Moves focus to last result in list |
|
|
120
|
+
| Enter | Executes the selected result |
|
|
121
|
+
| Escape | Closes the palette; returns focus to trigger element |
|
|
122
|
+
| Tab / Shift+Tab | Cycles focus within dialog (focus trap) |
|
|
123
|
+
|
|
124
|
+
### Accessibility Rules
|
|
125
|
+
|
|
126
|
+
- Dialog MUST have `aria-modal="true"` — prevents AT from reading content outside the palette
|
|
127
|
+
- `role="combobox"` input MUST have `aria-controls` pointing to the `role="listbox"` `id`
|
|
128
|
+
- `aria-live="polite"` on the results region ensures AT announces result count changes and empty state
|
|
129
|
+
- Focus MUST be trapped within the dialog while it is open (Tab/Shift+Tab cycle inside)
|
|
130
|
+
- On close, focus MUST return to the element that triggered the palette (not body)
|
|
131
|
+
- Keyboard shortcut hints (`⌘K`, `↵`) MUST be `aria-hidden="true"` — the shortcut must be registered separately
|
|
132
|
+
|
|
133
|
+
Cross-link: `reference/accessibility.md` — dialog focus trap, combobox pattern, live regions
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Motion
|
|
138
|
+
|
|
139
|
+
| Transition | Duration | Easing | Notes |
|
|
140
|
+
|------------|----------|--------|-------|
|
|
141
|
+
| Dialog open | 150ms | ease-out | Scale 0.96→1 + opacity 0→1 |
|
|
142
|
+
| Dialog close | 100ms | ease-in | Opacity 1→0 |
|
|
143
|
+
| Results update | 80ms | ease-out | Fade new results; avoid reflow |
|
|
144
|
+
| Backdrop fade in | 150ms | ease-out | Opacity 0→0.5 |
|
|
145
|
+
| Item selection flash | 80ms | ease-out | Brief fill before execute |
|
|
146
|
+
|
|
147
|
+
**BAN**: Do not animate result item reordering as the user types — causes visual instability and is disorienting for AT users with animations enabled.
|
|
148
|
+
|
|
149
|
+
Cross-link: `reference/motion.md` — dialog entry, BAN-04
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Do / Don't
|
|
154
|
+
|
|
155
|
+
### Do
|
|
156
|
+
- Trap focus inside the dialog while open *(WAI-ARIA APG dialog pattern, WCAG 2.1.2)*
|
|
157
|
+
- Set `aria-live="polite"` on the results region *(WCAG 4.1.3, Radix CMDK)*
|
|
158
|
+
- Return focus to the trigger element on close *(WAI-ARIA APG)*
|
|
159
|
+
- Use `role="group"` + `aria-label` for result sections *(WAI-ARIA APG, Linear, Raycast)*
|
|
160
|
+
|
|
161
|
+
### Don't
|
|
162
|
+
- Don't build a custom overlay without `role="dialog"` + `aria-modal` — screen readers will read background content *(WCAG 1.3.1)*
|
|
163
|
+
- Don't omit `aria-controls` on the combobox — AT cannot associate input with results *(WAI-ARIA APG combobox)*
|
|
164
|
+
- Don't open on hover or auto-focus — Cmd/Ctrl+K is the expected trigger *(Linear, Raycast convention)*
|
|
165
|
+
- Don't dismiss on every Escape keypress inside nested command flows — first Escape should exit sub-level, second closes palette *(Raycast, Linear)*
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Anti-patterns Cross-links
|
|
170
|
+
|
|
171
|
+
| Anti-pattern | Entry |
|
|
172
|
+
|--------------|-------|
|
|
173
|
+
| BAN-04 | `transition: all` on results list — `reference/anti-patterns.md#ban-04` |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Benchmark Citations
|
|
178
|
+
|
|
179
|
+
| Claim | Sources |
|
|
180
|
+
|-------|---------|
|
|
181
|
+
| role="dialog" + aria-modal="true" + focus trap | WAI-ARIA APG dialog pattern |
|
|
182
|
+
| role="combobox" + aria-controls for input | WAI-ARIA APG combobox pattern |
|
|
183
|
+
| Cmd/Ctrl+K universal trigger | Linear, Raycast, GitHub, VS Code (industry convention) |
|
|
184
|
+
| aria-live="polite" on results region | WAI-ARIA APG, WCAG 4.1.3 |
|
|
185
|
+
| role="group" + aria-label for result sections | WAI-ARIA APG, Radix CMDK |
|
|
186
|
+
|
|
187
|
+
Full system URLs: `connections/design-corpora.md`
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Grep Signatures
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Dialog overlay missing aria-modal
|
|
195
|
+
grep -rn 'command.*palette\|cmdk\|command-menu' src/ | grep 'role="dialog"' | grep -v 'aria-modal'
|
|
196
|
+
|
|
197
|
+
# Combobox missing aria-controls pointing to listbox
|
|
198
|
+
grep -rn 'role="combobox"' src/ | grep -v 'aria-controls'
|
|
199
|
+
|
|
200
|
+
# Results listbox missing id (needed for aria-controls target)
|
|
201
|
+
grep -rn 'role="listbox"' src/ | grep -v 'id='
|
|
202
|
+
|
|
203
|
+
# Missing aria-live on results region
|
|
204
|
+
grep -rn 'command.*palette\|cmd.*results\|cmdk' src/ | grep 'results\|listbox' | grep -v 'aria-live'
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Failing Example
|
|
210
|
+
|
|
211
|
+
```html
|
|
212
|
+
<!-- BAD: custom overlay with keyboard handling but no ARIA roles -->
|
|
213
|
+
<div class="command-palette" style="display:block"> <!-- no role="dialog", no aria-modal -->
|
|
214
|
+
<input type="text" placeholder="Search commands…"> <!-- no role="combobox", no aria-controls -->
|
|
215
|
+
<div class="results"> <!-- no role="listbox" -->
|
|
216
|
+
<div class="result-item active" onclick="execute('new-project')">
|
|
217
|
+
New Project
|
|
218
|
+
</div>
|
|
219
|
+
<div class="result-item" onclick="execute('invite')">
|
|
220
|
+
Invite member
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Why it fails**: No `role="dialog"` — AT does not treat this as a modal, and screen readers continue reading background content; no focus trap; input lacks `role="combobox"` and `aria-controls`; results have no `role="listbox"`; items have no `role="option"` or `aria-selected`; no `aria-live` region for result updates.
|
|
227
|
+
**Grep detection**: `grep -rn 'command.*palette\|cmdk\|command-menu' src/ | grep '<div' | grep -v 'role='`
|
|
228
|
+
**Fix**: Use `role="dialog"` + `aria-modal="true"` on the overlay; implement focus trap; add `role="combobox"` + `aria-controls` to input; add `role="listbox"` to results container; add `role="option"` + `aria-selected` to each item; add `aria-live="polite"` to results region.
|