@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.
- package/.claude-plugin/marketplace.json +9 -5
- package/.claude-plugin/plugin.json +19 -5
- package/CHANGELOG.md +122 -0
- package/README.md +41 -0
- package/SKILL.md +4 -1
- package/agents/component-benchmark-harvester.md +112 -0
- package/agents/component-benchmark-synthesizer.md +88 -0
- package/agents/design-auditor.md +60 -1
- package/agents/design-doc-writer.md +21 -0
- package/agents/design-executor.md +22 -4
- package/agents/design-pattern-mapper.md +61 -0
- package/agents/motion-mapper.md +74 -9
- package/agents/token-mapper.md +8 -0
- package/connections/design-corpora.md +158 -0
- package/package.json +13 -3
- package/reference/components/README.md +94 -0
- package/reference/components/TEMPLATE.md +184 -0
- package/reference/components/accordion.md +217 -0
- package/reference/components/alert.md +198 -0
- package/reference/components/badge.md +202 -0
- package/reference/components/breadcrumbs.md +198 -0
- package/reference/components/button.md +195 -0
- package/reference/components/card.md +200 -0
- package/reference/components/checkbox.md +207 -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/drawer.md +201 -0
- package/reference/components/file-upload.md +219 -0
- package/reference/components/input.md +208 -0
- package/reference/components/label.md +200 -0
- package/reference/components/link.md +193 -0
- package/reference/components/list.md +217 -0
- package/reference/components/menu.md +212 -0
- package/reference/components/modal-dialog.md +210 -0
- package/reference/components/navbar.md +211 -0
- package/reference/components/pagination.md +205 -0
- package/reference/components/popover.md +197 -0
- package/reference/components/progress.md +210 -0
- package/reference/components/radio.md +203 -0
- package/reference/components/rich-text-editor.md +226 -0
- package/reference/components/select-combobox.md +219 -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/switch.md +194 -0
- package/reference/components/table.md +229 -0
- package/reference/components/tabs.md +213 -0
- package/reference/components/toast.md +200 -0
- package/reference/components/tooltip.md +201 -0
- package/reference/components/tree.md +225 -0
- package/reference/css-grid-layout.md +835 -0
- package/reference/external/NOTICE.hyperframes +28 -0
- package/reference/image-optimization.md +582 -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/output-contracts/motion-map.schema.json +135 -0
- package/reference/registry.json +285 -0
- package/reference/registry.schema.json +6 -1
- 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
- package/skills/benchmark/SKILL.md +105 -0
|
@@ -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.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Date Picker — Benchmark Spec
|
|
2
|
+
|
|
3
|
+
**Harvested from**: Material 3, Carbon Design System, Atlassian Design System, Mantine DatePicker
|
|
4
|
+
**Wave**: 5 · **Category**: Advanced
|
|
5
|
+
**Spec file**: `reference/components/date-picker.md`
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
A Date Picker lets users select a single date or a date range through a calendar popover attached to a text input. It combines a formatted text field (for direct keyboard entry) with a visual month grid (for pointer or keyboard navigation). Use Date Picker when the date is human-meaningful and users may want to browse relative to a calendar context. Use a plain `<input type="date">` when native behavior is sufficient or a mobile-first audience is expected. *(Material 3, Carbon, Atlassian, Mantine agree: calendar popover + text input pairing is the canonical desktop pattern.)*
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Anatomy
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
[ Input field "MM/DD/YYYY" ] [ Calendar icon button ]
|
|
19
|
+
│
|
|
20
|
+
└── Popover (role="dialog", aria-modal="true")
|
|
21
|
+
├── Header: [ ← ] [ Month Year ] [ → ]
|
|
22
|
+
├── Day-of-week row (aria-hidden="true")
|
|
23
|
+
└── Month grid (role="grid")
|
|
24
|
+
└── Week rows (role="row")
|
|
25
|
+
└── Day cells (role="gridcell")
|
|
26
|
+
└── Day button (role="button")
|
|
27
|
+
|
|
28
|
+
Range variant adds a second input field for end date.
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Part | Required | Notes |
|
|
32
|
+
|------|----------|-------|
|
|
33
|
+
| Text input | Yes | Shows selected date; accepts direct keyboard entry |
|
|
34
|
+
| Format hint label | Yes | Visible "MM/DD/YYYY" supplement — NOT placeholder only |
|
|
35
|
+
| Calendar trigger button | Yes | Opens/closes popover; `aria-label="Open calendar"` |
|
|
36
|
+
| Popover (dialog) | Yes | `role="dialog"` + `aria-modal="true"` + focus trap |
|
|
37
|
+
| Month navigation | Yes | Previous/next month buttons; keyboard Page Up/Down |
|
|
38
|
+
| Month grid | Yes | `role="grid"` |
|
|
39
|
+
| Day cell | Yes | `role="gridcell"` containing `role="button"` for selectable days |
|
|
40
|
+
| Range highlight | No (range variant only) | Visual fill between start and end dates |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Variants
|
|
45
|
+
|
|
46
|
+
| Variant | Description | Systems |
|
|
47
|
+
|---------|-------------|---------|
|
|
48
|
+
| Single date | One input + calendar popover | Material 3, Carbon, Atlassian, Mantine |
|
|
49
|
+
| Date range | Start + end input pair sharing one calendar | Carbon (DateRangePicker), Mantine (DateRangePicker), Atlassian |
|
|
50
|
+
| Date-time | Date selection + time selector panel | Material 3 (DateTimePicker), Mantine |
|
|
51
|
+
| Inline calendar | Calendar always visible, no popover | Mantine (Calendar), Material 3 (docked) |
|
|
52
|
+
|
|
53
|
+
**Norm** (≥3/4 systems agree): popover calendar attached to a text input is the dominant pattern; range uses two inputs sharing one calendar panel.
|
|
54
|
+
**Diverge**: Material 3 uses a modal dialog for mobile; Carbon and Mantine use an anchored popover for all viewports.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## States
|
|
59
|
+
|
|
60
|
+
| State | Trigger | Visual | ARIA |
|
|
61
|
+
|-------|---------|--------|------|
|
|
62
|
+
| default | — | Resting input + trigger icon | — |
|
|
63
|
+
| open | Trigger click or input focus + Enter | Popover visible, focus moves inside | `aria-expanded="true"` on trigger |
|
|
64
|
+
| day hover | Pointer over day | Background highlight | — |
|
|
65
|
+
| day focus | Keyboard navigation | Focus-visible ring on day button | — |
|
|
66
|
+
| day selected | Enter/Space or click | Filled background (brand color) | `aria-pressed="true"` on day button |
|
|
67
|
+
| range-in-progress | Start selected, end not yet | Partial fill from start | — |
|
|
68
|
+
| range-complete | Both start and end selected | Full highlight between dates | — |
|
|
69
|
+
| disabled date | Date excluded by min/max/filter | Muted; `tabindex="-1"` | `aria-disabled="true"` on day button |
|
|
70
|
+
| error | Invalid date typed | Red border + error message | `aria-invalid="true"` on input |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Sizing & Spacing
|
|
75
|
+
|
|
76
|
+
| Size | Input Height | Popover Width | Day Cell Size | Font |
|
|
77
|
+
|------|-------------|---------------|---------------|------|
|
|
78
|
+
| sm | 32px | 256px | 32px | 13px |
|
|
79
|
+
| md (default) | 40px | 288px | 40px | 14px |
|
|
80
|
+
| lg | 48px | 320px | 44px | 16px |
|
|
81
|
+
|
|
82
|
+
**Norm**: 288px popover width fits a standard 7-column month grid with comfortable padding *(Carbon, Mantine)*.
|
|
83
|
+
Day cells must be ≥32px to meet minimum touch-target guidance; prefer 40px for mixed pointer/touch contexts.
|
|
84
|
+
|
|
85
|
+
Cross-link: `reference/surfaces.md` — minimum 44×44px accessible tap target via padding.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Typography
|
|
90
|
+
|
|
91
|
+
- Input text: body-md weight 400; monospace or tabular-nums variant for date digits aids alignment
|
|
92
|
+
- Day numbers: body-sm, center-aligned within cell
|
|
93
|
+
- Month/year header: body-md weight 600
|
|
94
|
+
- Format hint ("MM/DD/YYYY"): caption-sm, secondary color — attached as visible `<label>` supplement or `<span aria-hidden="true">` paired with `aria-describedby` on the input
|
|
95
|
+
|
|
96
|
+
Cross-link: `reference/typography.md` — tabular-nums for date/time fields.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Keyboard & Accessibility
|
|
101
|
+
|
|
102
|
+
> **WAI-ARIA role**: `dialog` (popover), `grid` (month table), `button` (day cells)
|
|
103
|
+
> **Required attributes**: `role="dialog"` + `aria-modal="true"` + `aria-label="Choose date"` on popover; `role="grid"` on month table; `role="gridcell"` + `role="button"` on each day
|
|
104
|
+
|
|
105
|
+
### Keyboard Contract
|
|
106
|
+
|
|
107
|
+
*Adapted from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/ and grid pattern — W3C — 2024*
|
|
108
|
+
|
|
109
|
+
| Key | Action |
|
|
110
|
+
|-----|--------|
|
|
111
|
+
| Enter / Space | Open calendar when focus is on trigger; select focused day |
|
|
112
|
+
| Arrow Left / Right | Move focus to previous / next day |
|
|
113
|
+
| Arrow Up / Down | Move focus to same day in previous / next week |
|
|
114
|
+
| Page Up | Navigate to previous month |
|
|
115
|
+
| Page Down | Navigate to next month |
|
|
116
|
+
| Ctrl + Page Up | Navigate to previous year |
|
|
117
|
+
| Ctrl + Page Down | Navigate to next year |
|
|
118
|
+
| Home | Move focus to first day of current week |
|
|
119
|
+
| End | Move focus to last day of current week |
|
|
120
|
+
| Escape | Close calendar popover; return focus to trigger button |
|
|
121
|
+
| Tab | Move focus through interactive elements inside popover |
|
|
122
|
+
|
|
123
|
+
### Accessibility Rules
|
|
124
|
+
|
|
125
|
+
- Calendar popover MUST use `role="dialog"` + `aria-modal="true"` — do not use a plain `<div>` overlay
|
|
126
|
+
- Focus MUST be trapped inside the open calendar; Escape closes and returns focus to the trigger
|
|
127
|
+
- Each selectable day MUST be a `<button>` (or element with `role="button"`) so keyboard and AT users can activate it
|
|
128
|
+
- Date format hint MUST be visible text, not only a placeholder (placeholder disappears on input and is not consistently announced)
|
|
129
|
+
- Disabled dates MUST use `aria-disabled="true"` and `tabindex="-1"` so they are skipped but still communicated to AT
|
|
130
|
+
- Range variant: announce selected range via `aria-label` on day buttons (e.g., `aria-label="April 15, 2025, start of range"`)
|
|
131
|
+
- Mobile: provide native `<input type="date">` fallback when touch device detected or user preference set
|
|
132
|
+
|
|
133
|
+
Cross-link: `reference/accessibility.md` — focus-trap pattern, dialog role requirements.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Motion
|
|
138
|
+
|
|
139
|
+
| Transition | Duration | Easing | Notes |
|
|
140
|
+
|------------|----------|--------|-------|
|
|
141
|
+
| Popover open | 150ms | ease-out | Fade + subtle scale from trigger |
|
|
142
|
+
| Popover close | 100ms | ease-in | Fade out |
|
|
143
|
+
| Month change | 200ms | ease-in-out | Slide left/right or cross-fade |
|
|
144
|
+
| Day selection | 80ms | ease-out | Fill background color |
|
|
145
|
+
| Range fill | 150ms | ease-out | Animate fill between dates |
|
|
146
|
+
|
|
147
|
+
**BAN**: Do not animate the calendar grid with a full-page slide — disorienting for keyboard users navigating by month.
|
|
148
|
+
|
|
149
|
+
Cross-link: `reference/motion.md` — reduced-motion: omit slide, keep instant day selection.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Do / Don't
|
|
154
|
+
|
|
155
|
+
### Do
|
|
156
|
+
- Show the date format as visible text ("MM/DD/YYYY") near the input *(Carbon, Atlassian)*
|
|
157
|
+
- Ensure keyboard users can navigate the entire calendar without a pointer *(WAI-ARIA APG)*
|
|
158
|
+
- Allow direct text entry in the input field — some users know the date and do not need the calendar *(Mantine, Carbon)*
|
|
159
|
+
- Support locale-aware first day of week (Sunday vs. Monday) and locale month/day names *(Material 3, Mantine)*
|
|
160
|
+
|
|
161
|
+
### Don't
|
|
162
|
+
- Don't use `<table>` with click-only cells — add `role="grid"` and full keyboard navigation *(WCAG 2.1 §2.1.1)*
|
|
163
|
+
- Don't use placeholder text alone to convey the date format — placeholder vanishes on input *(Carbon, Atlassian)*
|
|
164
|
+
- Don't trap focus permanently — Escape must always close the popover and restore focus *(WAI-ARIA APG)*
|
|
165
|
+
- Don't omit the native `<input type="date">` for mobile — custom calendars are unusable on small touch screens *(Material 3)*
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Anti-patterns Cross-links
|
|
170
|
+
|
|
171
|
+
| Anti-pattern | Entry |
|
|
172
|
+
|--------------|-------|
|
|
173
|
+
| BAN-07 | Placeholder as only label/hint — `reference/anti-patterns.md#ban-07` |
|
|
174
|
+
| BAN-12 | Custom overlay without focus trap — `reference/anti-patterns.md#ban-12` |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Benchmark Citations
|
|
179
|
+
|
|
180
|
+
| Claim | Sources |
|
|
181
|
+
|-------|---------|
|
|
182
|
+
| Popover uses role="dialog" + aria-modal | WAI-ARIA APG dialog pattern, Carbon, Atlassian |
|
|
183
|
+
| Month grid uses role="grid" | WAI-ARIA APG grid pattern, Mantine docs |
|
|
184
|
+
| Arrow keys navigate days; Page Up/Down change month | WAI-ARIA APG, Carbon DatePicker keyboard docs |
|
|
185
|
+
| Format hint must be visible text, not only placeholder | Carbon, Atlassian design guidelines |
|
|
186
|
+
| Native input fallback for mobile | Material 3, Mantine (mobile prop) |
|
|
187
|
+
|
|
188
|
+
Full system URLs: `connections/design-corpora.md`
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Grep Signatures
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Calendar popover missing role="dialog" (uses plain div overlay)
|
|
196
|
+
grep -rn 'calendar\|datepicker\|date-picker' src/ | grep -v 'role="dialog"'
|
|
197
|
+
|
|
198
|
+
# Day buttons missing keyboard handler (click-only calendar)
|
|
199
|
+
grep -rn 'role="gridcell"\|\.day\|\.calendar-day' src/ | grep -v 'onKeyDown\|on:keydown\|handleKey'
|
|
200
|
+
|
|
201
|
+
# Date input using placeholder for format hint only (no visible label supplement)
|
|
202
|
+
grep -rn '<input.*type="text".*date\|DateInput' src/ | grep 'placeholder.*MM\|placeholder.*dd' | grep -v 'aria-describedby\|<label\|hint'
|
|
203
|
+
|
|
204
|
+
# Missing aria-modal on calendar popover
|
|
205
|
+
grep -rn 'role="dialog"' src/ | grep -v 'aria-modal'
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Failing Example
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<!-- BAD: calendar using <table> with click-only day cells, no keyboard navigation -->
|
|
214
|
+
<table class="calendar">
|
|
215
|
+
<tbody>
|
|
216
|
+
<tr>
|
|
217
|
+
<td class="day" onclick="selectDate('2025-04-01')">1</td>
|
|
218
|
+
<td class="day" onclick="selectDate('2025-04-02')">2</td>
|
|
219
|
+
<!-- ... -->
|
|
220
|
+
</tr>
|
|
221
|
+
</tbody>
|
|
222
|
+
</table>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Why it fails**: `<td>` cells are not focusable by default; no keyboard navigation; AT users cannot select dates; no `role="grid"` or `role="gridcell"`; click handler is the only interaction path.
|
|
226
|
+
**Grep detection**: `grep -rn '<table.*calendar\|<td.*onclick.*date\|<td.*selectDate' src/`
|
|
227
|
+
**Fix**: Replace with `<table role="grid">`, `<td role="gridcell">`, `<button>` inside each cell, and full Arrow/Page/Home/End keyboard handlers. Add `role="dialog"` + `aria-modal="true"` + focus trap on the popover wrapper.
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Drawer / Sheet — Benchmark Spec
|
|
2
|
+
|
|
3
|
+
**Harvested from**: Material 3, Polaris (Sheet), Carbon, Atlassian, Mantine, shadcn/ui, Headless UI, Apple HIG
|
|
4
|
+
**Wave**: 2 · **Category**: Containers
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
A drawer (or sheet) is a panel that slides in from an edge of the viewport. It is less disruptive than a modal for workflows that benefit from co-existing with the background — detail panels, navigation menus, filter sidebars, multi-step flows. Like a modal, it traps focus and requires Escape to close. Unlike a modal, the backdrop is optional and can be semi-transparent. *(Material 3, Carbon, Polaris all position drawer as less disruptive than modal)*
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Anatomy
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Side drawer (right):
|
|
18
|
+
┌────────────────┬──────────────┐
|
|
19
|
+
│ │ [✕] Title │
|
|
20
|
+
│ Page │──────────────│
|
|
21
|
+
│ content │ Body │
|
|
22
|
+
│ (inert) │ content │
|
|
23
|
+
│ │──────────────│
|
|
24
|
+
│ │ [Actions] │
|
|
25
|
+
└────────────────┴──────────────┘
|
|
26
|
+
|
|
27
|
+
Bottom sheet (mobile):
|
|
28
|
+
┌──────────────────────────────┐
|
|
29
|
+
│ Page content │
|
|
30
|
+
├──────────────────────────────┤ ← handle / drag indicator
|
|
31
|
+
│ Sheet content │ ← slides up; partial height
|
|
32
|
+
└──────────────────────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Part | Required | Notes |
|
|
36
|
+
|------|----------|-------|
|
|
37
|
+
| Panel container | Yes | `role="dialog"` + `aria-modal="true"` |
|
|
38
|
+
| Title | Yes | `id` → `aria-labelledby` on panel |
|
|
39
|
+
| Close button | Yes | Top-right; keyboard accessible |
|
|
40
|
+
| Backdrop | Conditional | Semi-transparent; may click-to-close (configurable) |
|
|
41
|
+
| Drag handle | No | Bottom sheet only; swipe gesture affordance |
|
|
42
|
+
| Scroll container | Conditional | Body scrollable when content exceeds height |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Variants
|
|
47
|
+
|
|
48
|
+
| Variant | Direction | Use case | Systems |
|
|
49
|
+
|---------|-----------|----------|---------|
|
|
50
|
+
| Right side | Slides from right | Detail panels, settings | All |
|
|
51
|
+
| Left side | Slides from left | Navigation menus | Material 3, Carbon |
|
|
52
|
+
| Bottom sheet | Slides from bottom | Mobile actions, filters | Material 3, Apple HIG |
|
|
53
|
+
| Top | Slides from top | Notifications, alerts | Rare; avoid |
|
|
54
|
+
| Full-height | 100vh, pushes content | Persistent navigation | Material 3 |
|
|
55
|
+
| Partial height | 60–80vh, overlays | Mobile bottom sheet | Apple HIG, Material 3 |
|
|
56
|
+
|
|
57
|
+
**Norm** (≥5/18): right-side is default; bottom sheet for mobile.
|
|
58
|
+
**Diverge**: backdrop-click-to-close — same debate as modal; for navigation drawers, backdrop click should close; for form/detail drawers, configurable.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## States
|
|
63
|
+
|
|
64
|
+
Same as Modal/Dialog — see `modal-dialog.md`. Key differences:
|
|
65
|
+
|
|
66
|
+
| State | Drawer-specific |
|
|
67
|
+
|-------|-----------------|
|
|
68
|
+
| open | Slides in from edge; `aria-expanded="true"` on trigger (nav drawer) |
|
|
69
|
+
| closed | Slides out; `aria-expanded="false"` |
|
|
70
|
+
| partial (bottom sheet) | Dragged to partial height; swipe-up to expand |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Sizing & Spacing
|
|
75
|
+
|
|
76
|
+
| Variant | Width / Height | Notes |
|
|
77
|
+
|---------|---------------|-------|
|
|
78
|
+
| Right side | 400–480px (desktop), 100% (mobile) | `min-width: 280px` |
|
|
79
|
+
| Left side (nav) | 240–320px | 256px is common (Carbon, Material 3) |
|
|
80
|
+
| Bottom sheet | 60–100vh | Drag handle at 12px × 36px |
|
|
81
|
+
| Padding | 20–24px | Match modal padding |
|
|
82
|
+
|
|
83
|
+
Cross-link: `reference/surfaces.md` — shadow on drawer edge (unilateral shadow)
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Typography
|
|
88
|
+
|
|
89
|
+
- Title: 16–18px/600
|
|
90
|
+
- Body: 14px/400
|
|
91
|
+
- Section headers within body: 12px/600 uppercase muted
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Keyboard & Accessibility
|
|
96
|
+
|
|
97
|
+
> **WAI-ARIA role**: `dialog` (same as modal — drawer is a type of dialog)
|
|
98
|
+
> **Required attributes**: `aria-modal="true"`, `aria-labelledby` (title id)
|
|
99
|
+
|
|
100
|
+
### Keyboard Contract
|
|
101
|
+
|
|
102
|
+
*Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/ — W3C — 2024*
|
|
103
|
+
|
|
104
|
+
Same Tab/Shift+Tab/Escape contract as modal (see `modal-dialog.md`).
|
|
105
|
+
|
|
106
|
+
### Drawer-specific Accessibility Rules
|
|
107
|
+
|
|
108
|
+
- Focus trap: MUST trap focus inside the drawer while open — same as modal
|
|
109
|
+
- On open: focus moves to first focusable element (or close button if no primary action)
|
|
110
|
+
- On close: focus MUST return to the element that triggered the drawer open
|
|
111
|
+
- Navigation drawer (`role="navigation"`): if the drawer IS the main nav, use `role="navigation"` + `aria-label="Main"` instead of `role="dialog"`; different keyboard contract (no focus trap — it IS a landmark)
|
|
112
|
+
- Background `inert`: set `inert` attribute (or equivalent) on background content when drawer is open
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Motion
|
|
117
|
+
|
|
118
|
+
| Transition | Duration | Easing | Notes |
|
|
119
|
+
|------------|----------|--------|-------|
|
|
120
|
+
| Slide in (right) | 250ms | ease-out (cubic-bezier 0.4,0,0.2,1) | |
|
|
121
|
+
| Slide out (right) | 200ms | ease-in | |
|
|
122
|
+
| Bottom sheet expand | 300ms | spring (bounce: 0) | |
|
|
123
|
+
| Backdrop fade | 200ms | ease | opacity 0→0.4 |
|
|
124
|
+
|
|
125
|
+
Swipe-to-close (bottom sheet): detect `pointerup` with velocity + displacement threshold.
|
|
126
|
+
Cross-link: `reference/motion.md` — spring bounce=0, `prefers-reduced-motion` (disable slide, instant toggle)
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Do / Don't
|
|
131
|
+
|
|
132
|
+
### Do
|
|
133
|
+
- Trap focus inside the drawer when open — same rule as modal *(WAI-ARIA APG)*
|
|
134
|
+
- Return focus to the trigger element on close *(WAI-ARIA APG, Radix, Mantine)*
|
|
135
|
+
- Use right-side drawer for content-detail panels; left-side for navigation *(Material 3, Carbon)*
|
|
136
|
+
- Support swipe-to-close on bottom sheets for mobile *(Apple HIG, Material 3)*
|
|
137
|
+
|
|
138
|
+
### Don't
|
|
139
|
+
- Don't use `role="navigation"` for content drawers — only for navigation-purpose drawers *(WAI-ARIA APG)*
|
|
140
|
+
- Don't let Tab escape the drawer while it's open *(WAI-ARIA APG)*
|
|
141
|
+
- Don't disable background scroll without setting `overflow:hidden` on body *(all systems)*
|
|
142
|
+
- Don't slide from top for content — top drawers conflict with browser UI and notifications *(Material 3)*
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Anti-patterns Cross-links
|
|
147
|
+
|
|
148
|
+
| Anti-pattern | Entry |
|
|
149
|
+
|--------------|-------|
|
|
150
|
+
| Focus escaping drawer | `reference/anti-patterns.md` |
|
|
151
|
+
| No focus return on close | `reference/anti-patterns.md` |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Benchmark Citations
|
|
156
|
+
|
|
157
|
+
| Claim | Sources |
|
|
158
|
+
|-------|---------|
|
|
159
|
+
| role="dialog" for content drawers | WAI-ARIA APG, Radix |
|
|
160
|
+
| Focus trap required | WAI-ARIA APG |
|
|
161
|
+
| Swipe-to-close on bottom sheet | Apple HIG, Material 3 |
|
|
162
|
+
| 256px left nav width | Carbon, Material 3 |
|
|
163
|
+
| ease-out 250ms slide-in | Material 3 motion spec |
|
|
164
|
+
|
|
165
|
+
Full system URLs: `connections/design-corpora.md`
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Grep Signatures
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Drawer without focus trap
|
|
173
|
+
grep -rn 'drawer\|sheet\|sidebar' src/ | grep -L 'FocusTrap\|focus-trap\|inert'
|
|
174
|
+
|
|
175
|
+
# Drawer missing aria-modal
|
|
176
|
+
grep -rn 'class.*drawer\|class.*sheet' src/ | grep 'role="dialog"' | grep -v 'aria-modal'
|
|
177
|
+
|
|
178
|
+
# Background scroll not prevented
|
|
179
|
+
grep -rn 'drawer.*open\|isOpen.*drawer' src/ | grep -v 'overflow\|body\.'
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Failing Example
|
|
185
|
+
|
|
186
|
+
```jsx
|
|
187
|
+
// BAD: drawer panel with no focus management — Tab escapes to background
|
|
188
|
+
function Drawer({ isOpen }) {
|
|
189
|
+
return isOpen ? (
|
|
190
|
+
<div className="drawer-panel">
|
|
191
|
+
<button onClick={close}>✕</button>
|
|
192
|
+
<h2>Settings</h2>
|
|
193
|
+
<SettingsForm />
|
|
194
|
+
</div>
|
|
195
|
+
) : null;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Why it fails**: No `role="dialog"`, no `aria-modal`, no focus trap. Tab navigates freely into the background while the drawer is open. Escape does nothing.
|
|
200
|
+
**Grep detection**: `grep -rn 'class.*drawer\|class.*panel' src/ | grep -v 'role=\|aria-modal'`
|
|
201
|
+
**Fix**: Use Radix `<Dialog>` with `data-side` variant, or Vaul (drawer library), which handles focus trap, Escape, portal, and `aria-modal` automatically.
|