@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.
Files changed (58) hide show
  1. package/.claude-plugin/marketplace.json +12 -4
  2. package/.claude-plugin/plugin.json +22 -4
  3. package/CHANGELOG.md +111 -0
  4. package/README.md +27 -2
  5. package/agents/design-auditor.md +65 -1
  6. package/agents/design-context-builder.md +6 -1
  7. package/agents/design-doc-writer.md +21 -0
  8. package/agents/design-executor.md +22 -4
  9. package/agents/design-pattern-mapper.md +62 -0
  10. package/agents/design-phase-researcher.md +1 -1
  11. package/agents/motion-mapper.md +74 -9
  12. package/agents/token-mapper.md +8 -0
  13. package/package.json +16 -2
  14. package/reference/components/README.md +27 -23
  15. package/reference/components/alert.md +198 -0
  16. package/reference/components/badge.md +202 -0
  17. package/reference/components/breadcrumbs.md +198 -0
  18. package/reference/components/chip.md +209 -0
  19. package/reference/components/command-palette.md +228 -0
  20. package/reference/components/date-picker.md +227 -0
  21. package/reference/components/file-upload.md +219 -0
  22. package/reference/components/list.md +217 -0
  23. package/reference/components/menu.md +212 -0
  24. package/reference/components/navbar.md +211 -0
  25. package/reference/components/pagination.md +205 -0
  26. package/reference/components/progress.md +210 -0
  27. package/reference/components/rich-text-editor.md +226 -0
  28. package/reference/components/sidebar.md +211 -0
  29. package/reference/components/skeleton.md +197 -0
  30. package/reference/components/slider.md +208 -0
  31. package/reference/components/stepper.md +220 -0
  32. package/reference/components/table.md +229 -0
  33. package/reference/components/toast.md +200 -0
  34. package/reference/components/tree.md +225 -0
  35. package/reference/css-grid-layout.md +835 -0
  36. package/reference/data-visualization.md +333 -0
  37. package/reference/external/NOTICE.hyperframes +28 -0
  38. package/reference/form-patterns.md +245 -0
  39. package/reference/image-optimization.md +582 -0
  40. package/reference/information-architecture.md +255 -0
  41. package/reference/motion-advanced.md +754 -0
  42. package/reference/motion-easings.md +381 -0
  43. package/reference/motion-interpolate.md +282 -0
  44. package/reference/motion-spring.md +234 -0
  45. package/reference/motion-transition-taxonomy.md +155 -0
  46. package/reference/motion.md +20 -0
  47. package/reference/onboarding-progressive-disclosure.md +250 -0
  48. package/reference/output-contracts/motion-map.schema.json +135 -0
  49. package/reference/platforms.md +346 -0
  50. package/reference/registry.json +445 -220
  51. package/reference/registry.schema.json +4 -0
  52. package/reference/rtl-cjk-cultural.md +353 -0
  53. package/reference/user-research.md +360 -0
  54. package/reference/variable-fonts-loading.md +532 -0
  55. package/scripts/lib/easings.cjs +280 -0
  56. package/scripts/lib/parse-contract.cjs +220 -0
  57. package/scripts/lib/spring.cjs +160 -0
  58. package/scripts/tests/test-motion-provenance.sh +64 -0
@@ -25,6 +25,10 @@ You inventory motion and animation patterns. Zero session memory. You do not mod
25
25
 
26
26
  - `.design/STATE.md`
27
27
  - `reference/motion.md` (if present)
28
+ - `reference/motion-advanced.md` (if present) — advanced patterns: spring physics, scroll-driven, FLIP, View Transitions API, gesture/drag mechanics, clip-path patterns, blur crossfades, Framer Motion hardware-accel gotcha
29
+ - `reference/motion-easings.md` (if present) — 12 canonical easing presets; classify each detected easing against this catalog
30
+ - `reference/motion-transition-taxonomy.md` (if present) — 8 transition families; classify page/route transitions against this taxonomy
31
+ - `reference/motion-spring.md` (if present) — spring presets; classify spring configs against gentle/wobbly/stiff/slow
28
32
  - Any files supplied by the orchestrator
29
33
 
30
34
  ## Scan Strategy
@@ -61,8 +65,37 @@ From the collected values, bucket by:
61
65
  - Normal: 200–400ms
62
66
  - Slow: >400ms
63
67
 
68
+ ## Advanced Scan Patterns (Phase 18+)
69
+
70
+ When `reference/motion-advanced.md` is present, additionally scan for:
71
+
72
+ ```bash
73
+ # Gesture / drag patterns
74
+ grep -rEn "setPointerCapture|onPointerDown.*drag|dragConstraints|useDragControls" src/ | head -40
75
+ grep -rEn "velocity|flick|swipe.*dismiss|drag.*dismiss" src/ | head -40
76
+
77
+ # Clip-path animations
78
+ grep -rEn "clip-path|clipPath|inset\(" src/ | head -40
79
+
80
+ # FLIP / View Transitions
81
+ grep -rEn "layoutId|startViewTransition|view-transition-name" src/ | head -30
82
+
83
+ # Scroll-driven
84
+ grep -rEn "animation-timeline|ScrollTimeline|useScroll\b" src/ | head -30
85
+
86
+ # WAAPI
87
+ grep -rEn "\.animate\(\[|WebAnimation|getAnimations" src/ | head -20
88
+ ```
89
+
90
+ Classify gesture patterns against `reference/motion-advanced.md` (velocity formula, pointer capture, multi-touch protection).
91
+ Classify easing values against the 12 canonical presets in `reference/motion-easings.md`; output `"custom"` with justification for anything that doesn't match.
92
+ Classify page/route transitions against the 8 families in `reference/motion-transition-taxonomy.md`.
93
+ Classify spring configs against the 4 presets in `reference/motion-spring.md`.
94
+
64
95
  ## Output Format — `.design/map/motion.md`
65
96
 
97
+ **The output MUST begin with a structured JSON block** enclosed in ` ```json ``` ` fences, followed by the prose sections. The JSON block must conform to `reference/output-contracts/motion-map.schema.json`. Malformed or missing blocks are validation failures.
98
+
66
99
  ```markdown
67
100
  ---
68
101
  generated: [ISO 8601]
@@ -70,22 +103,54 @@ generated: [ISO 8601]
70
103
 
71
104
  # Motion Map
72
105
 
106
+ ```json
107
+ {
108
+ "schema_version": "1.0.0",
109
+ "generated_at": "[ISO 8601]",
110
+ "summary": {
111
+ "total_animations": 0,
112
+ "custom_easings": 0,
113
+ "reduced_motion_compliant": false,
114
+ "libraries": []
115
+ },
116
+ "animations": [
117
+ {
118
+ "id": "example-toast-enter",
119
+ "location": { "file": "src/components/Toast.tsx", "line": 12 },
120
+ "description": "Toast enter animation — opacity + translateY",
121
+ "easing": "cubic-out",
122
+ "duration_class": "quick",
123
+ "duration_ms": 180,
124
+ "trigger": "state-change",
125
+ "library": "framer-motion",
126
+ "reduced_motion_handled": true
127
+ }
128
+ ]
129
+ }
130
+ ```
131
+
73
132
  ## CSS transitions
74
- | File | Property | Duration | Easing |
75
- |------|----------|----------|--------|
133
+ | File | Property | Duration | Easing | Canonical Easing |
134
+ |------|----------|----------|--------|-----------------|
76
135
 
77
136
  ## Library usage
78
137
  | Library | Files | Notes |
79
138
  |---------|-------|-------|
80
139
 
81
140
  ## Duration distribution
82
- - Fast (<200ms): [N]
83
- - Normal (200400ms): [N]
84
- - Slow (>400ms): [N]
85
-
86
- ## Easing functions
87
- | Easing | Count |
88
- |--------|-------|
141
+ - Instant (<100ms): [N]
142
+ - Quick (100200ms): [N]
143
+ - Standard (200–400ms): [N]
144
+ - Slow (400–800ms): [N]
145
+ - Narrative (>800ms): [N]
146
+
147
+ ## Easing classification
148
+ | Detected Easing | Canonical Name | Count | Notes |
149
+ |----------------|---------------|-------|-------|
150
+
151
+ ## Advanced patterns detected
152
+ | Pattern | Files | Notes |
153
+ |---------|-------|-------|
89
154
 
90
155
  ## Reduced-motion compliance
91
156
  - `prefers-reduced-motion` queries present: [N]
@@ -119,6 +119,14 @@ After the standard token inventory, scan and report:
119
119
  - Check if they have `font-variant-numeric: tabular-nums` or Tailwind `tabular-nums` class
120
120
  - Report missing instances
121
121
 
122
+ 5. **Easing token consolidation** (when `reference/motion-easings.md` is present)
123
+ - Grep: `cubic-bezier\(` in CSS/SCSS/styled-components/Tailwind config
124
+ - Grep: `ease:` or `easing:` in Framer Motion / GSAP configs
125
+ - For each raw easing value found, check if a canonical `--ease-*` token from `reference/motion-easings.md` would serve the same purpose
126
+ - Report raw values that map to a canonical preset (recommend the `--ease-*` token)
127
+ - Report raw values with no canonical match as informational (may warrant a custom token)
128
+ - Do NOT flag values that are already using `--ease-*` tokens
129
+
122
130
  ### Output format:
123
131
  ```
124
132
  ## Micro-polish token findings
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hegemonart/get-design-done",
3
- "version": "1.16.0",
3
+ "version": "1.19.0",
4
4
  "description": "A Claude Code plugin for systematic design improvement",
5
5
  "author": "Hegemon",
6
6
  "homepage": "https://github.com/hegemonart/get-design-done",
@@ -67,8 +67,22 @@
67
67
  "style-vocabulary",
68
68
  "industry-palettes",
69
69
  "ui-style-vocabulary",
70
+ "variable-fonts",
71
+ "container-queries",
72
+ "view-transitions",
73
+ "motion-vocabulary",
74
+ "motion-easings",
75
+ "transition-taxonomy",
76
+ "gesture-mechanics",
77
+ "clip-path-animation",
70
78
  "component-specs",
71
- "design-system-benchmarks"
79
+ "design-system-benchmarks",
80
+ "i18n",
81
+ "user-research",
82
+ "information-architecture",
83
+ "form-patterns",
84
+ "data-viz",
85
+ "platforms"
72
86
  ],
73
87
  "skills": [
74
88
  "SKILL.md"
@@ -41,40 +41,44 @@ Typography · Keyboard/a11y · Motion · Do/Don't · Anti-patterns · Citations
41
41
 
42
42
  ---
43
43
 
44
- ## Wave 3 — Feedback *(Phase 17)*
44
+ ## Wave 3 — Feedback
45
45
 
46
46
  | Component | Spec | Purpose |
47
47
  |-----------|------|---------|
48
- | Toast / Snackbar | | Ephemeral status notification |
49
- | Alert / Banner | | Persistent inline status message |
50
- | Progress | | Linear and circular completion indicator |
51
- | Skeleton | | Loading placeholder matching content shape |
52
- | Badge | | Numeric or status indicator overlaid on another element |
53
- | Chip / Tag | | Compact, dismissible label |
48
+ | Toast / Snackbar | [toast.md](toast.md) | Ephemeral status notification; auto-dismisses 4–8s |
49
+ | Alert / Banner | [alert.md](alert.md) | Persistent inline status message; four severity variants |
50
+ | Progress | [progress.md](progress.md) | Linear and circular completion indicator; determinate + indeterminate |
51
+ | Skeleton | [skeleton.md](skeleton.md) | Loading placeholder matching content shape |
52
+ | Badge | [badge.md](badge.md) | Numeric or status indicator overlaid on another element |
53
+ | Chip / Tag | [chip.md](chip.md) | Compact toggleable/removable label; filter, input, suggestion, display |
54
54
 
55
55
  ---
56
56
 
57
- ## Wave 4 — Navigation *(Phase 17)*
57
+ ## Wave 4 — Navigation & Data *(v1.17.0 · plan 17-02)*
58
58
 
59
59
  | Component | Spec | Purpose |
60
60
  |-----------|------|---------|
61
- | Breadcrumb | | Hierarchical location trail |
62
- | Pagination | | Page-set navigation |
63
- | Stepper | | Sequential multi-step progress indicator |
64
- | Navigation Menu | | Top-level or sidebar navigation |
65
- | Command Menu | | Keyboard-first search + action launcher |
61
+ | Menu | [menu.md](menu.md) | Dropdown and context menu; action list with ARIA menu roles |
62
+ | Navbar | [navbar.md](navbar.md) | Top navigation bar; primary nav + skip link + mobile hamburger |
63
+ | Sidebar | [sidebar.md](sidebar.md) | Collapsible side navigation panel; icon+label / icon-only states |
64
+ | Breadcrumbs | [breadcrumbs.md](breadcrumbs.md) | Hierarchical location trail; aria-current + hidden separators |
65
+ | Pagination | [pagination.md](pagination.md) | Page-set navigation; previous/next + page buttons + per-page |
66
+ | Table | [table.md](table.md) | Data table with sorting, selection, sticky header, virtualisation |
67
+ | List | [list.md](list.md) | Display list (`<ul>/<ol>`) and interactive listbox (`role="listbox"`) |
68
+ | Tree | [tree.md](tree.md) | Hierarchical tree view; expand/collapse with full ARIA tree roles |
69
+ | Command Palette | [command-palette.md](command-palette.md) | Global Cmd/Ctrl+K launcher; dialog + combobox + listbox pattern |
66
70
 
67
71
  ---
68
72
 
69
- ## Wave 5 — Data & Advanced *(Phase 17)*
73
+ ## Wave 5 — Data & Advanced
70
74
 
71
75
  | Component | Spec | Purpose |
72
76
  |-----------|------|---------|
73
- | Table / Data Grid | | Tabular data with sorting, filtering, selection |
74
- | Date Picker | | Calendar-based date/range selection |
75
- | File Upload | | Drag-drop or browse file input |
76
- | Rich Text Editor | | WYSIWYG content authoring |
77
- | Virtualized List | | Windowed rendering for large datasets |
77
+ | Date Picker | [date-picker.md](date-picker.md) | Calendar-based date/range selection; input + popover variants |
78
+ | Slider | [slider.md](slider.md) | Single-value and range thumb on a track; full keyboard contract |
79
+ | File Upload | [file-upload.md](file-upload.md) | Drag-drop zone + accessible file input; per-file progress list |
80
+ | Rich-Text Editor | [rich-text-editor.md](rich-text-editor.md) | WYSIWYG authoring with contenteditable + toolbar + mentions |
81
+ | Stepper / Wizard | [stepper.md](stepper.md) | Sequential multi-step flow; role="list" (not tablist) |
78
82
 
79
83
  ---
80
84
 
@@ -84,7 +88,7 @@ Typography · Keyboard/a11y · Motion · Do/Don't · Anti-patterns · Citations
84
88
  |------|-------|--------|
85
89
  | Wave 1 — Inputs | 8 | v1.16.0 |
86
90
  | Wave 2 — Containers | 7 | v1.16.0 |
87
- | Wave 3 — Feedback | 6 | Phase 17 |
88
- | Wave 4 — Navigation | 5 | Phase 17 |
89
- | Wave 5 — Data & Advanced | 5 | Phase 17 |
90
- | **Total** | **31** | — |
91
+ | Wave 3 — Feedback | 6 | v1.17.0 |
92
+ | Wave 4 — Navigation & Data | 9 | v1.17.0 |
93
+ | Wave 5 — Data & Advanced | 5 | v1.17.0 |
94
+ | **Total** | **35** | — |
@@ -0,0 +1,198 @@
1
+ # Alert / Banner — Benchmark Spec
2
+
3
+ **Harvested from**: Material 3 (Banner), Carbon (InlineNotification), Polaris (Banner), Atlassian (SectionMessage)
4
+ **Wave**: 3 · **Category**: Feedback
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ An alert (inline notification or banner) is a persistent in-page message that communicates status, warnings, or errors relevant to the current context. Unlike Toast, it does not auto-dismiss — it remains visible until the user dismisses it or the underlying condition resolves. Use alert for messages that require acknowledgement or that must remain visible for reference. *(Material 3, Carbon, Polaris, Atlassian agree: alert = persistent inline feedback)*
11
+
12
+ ---
13
+
14
+ ## Anatomy
15
+
16
+ ```
17
+ ┌─────────────────────────────────────────────────────────┐
18
+ │ [icon] [Title (optional)] [✕ dismiss?]│
19
+ │ Message text │
20
+ │ [Primary action?] [Secondary action?] │
21
+ └─────────────────────────────────────────────────────────┘
22
+ ↑ role="alert" (error/warning) or role="status" (info/success)
23
+ ```
24
+
25
+ | Part | Required | Notes |
26
+ |------|----------|-------|
27
+ | Container | Yes | `role="alert"` or `role="status"` per severity |
28
+ | Severity icon | Yes | Visual + semantic variant indicator; never color alone |
29
+ | Message text | Yes | Concise description of status or error |
30
+ | Title | No | Recommended for error/warning; provides quick scanning |
31
+ | Primary action | No | One CTA linking to resolution (e.g. "Retry", "Review") |
32
+ | Secondary action | No | Supplemental link; lower emphasis |
33
+ | Dismiss button | No | Required when alert can be resolved by user |
34
+
35
+ ---
36
+
37
+ ## Variants
38
+
39
+ | Variant | Description | Systems |
40
+ |---------|-------------|---------|
41
+ | Info | Blue; neutral informational message; `role="status"` polite | All |
42
+ | Success | Green; completed action confirmation; `role="status"` polite | All |
43
+ | Warning | Amber; potentially harmful condition; `role="alert"` assertive | All |
44
+ | Error | Red; failure or blocking condition; `role="alert"` assertive | All |
45
+
46
+ **Norm** (≥4/18 systems agree): four-variant semantic model (info/success/warning/error) with matching color, icon, and role. Icon is REQUIRED — color alone violates WCAG 1.4.1.
47
+ **Diverge**: Atlassian names this "SectionMessage" and omits dismiss; Carbon always includes an X; Polaris supports titled and untitled variants; Material 3 "Banner" supports one CTA only.
48
+
49
+ ---
50
+
51
+ ## States
52
+
53
+ | State | Trigger | Visual | ARIA |
54
+ |-------|---------|--------|------|
55
+ | visible | render | Full opacity, inline position | `role="alert"` or `role="status"` |
56
+ | hover | pointer over action | Action underline / background change | — |
57
+ | focus | keyboard on action/dismiss | focus-visible ring on button | — |
58
+ | dismissed | click dismiss button | Collapsed / removed | Removed from DOM |
59
+
60
+ ---
61
+
62
+ ## Sizing & Spacing
63
+
64
+ | Placement | Width | Radius | Padding |
65
+ |-----------|-------|--------|---------|
66
+ | Full-width (page/section) | 100% of container | 0px | 16px 20px |
67
+ | Inline / card-contained | fit-to-container | 6–8px | 12px 16px |
68
+
69
+ | Property | Value | Notes |
70
+ |----------|-------|-------|
71
+ | Icon size | 20px | Aligned to first line of text |
72
+ | Gap (icon → text) | 12px | |
73
+ | Min height | 48px single-line | Grows with content |
74
+
75
+ **Norm**: Full-width placement in page-level contexts; rounded corners for component-level inline placement *(Material 3, Carbon, Atlassian)*.
76
+
77
+ ---
78
+
79
+ ## Typography
80
+
81
+ - Title: label-md (14px/600) — gives quick scannable context for complex errors
82
+ - Message: body-sm (14px/400) — readable at inline scale
83
+ - Action: label-sm (13px/500) — matches action button label weight
84
+
85
+ Cross-link: `reference/typography.md` — body-sm, label-md definitions
86
+
87
+ ---
88
+
89
+ ## Keyboard & Accessibility
90
+
91
+ > **WAI-ARIA role**: `alert` (error/warning) or `status` (info/success)
92
+ > **Required attributes**: `role` on container; icon must have `aria-hidden="true"` + text-based variant reinforcement
93
+
94
+ ### Keyboard Contract
95
+
96
+ *Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/patterns/alert/ — W3C — 2024*
97
+
98
+ | Key | Action |
99
+ |-----|--------|
100
+ | Tab | Moves focus to action buttons or dismiss button in document order |
101
+ | Enter / Space | Activates focused action or dismiss button |
102
+ | Escape | No special behavior (unlike modal); alert stays visible |
103
+
104
+ The alert container itself is not focusable. Child buttons follow standard button keyboard contract.
105
+
106
+ ### Accessibility Rules
107
+
108
+ - Icon MUST be `aria-hidden="true"` — the icon is decorative reinforcement; the `role` and text carry the semantic meaning *(WCAG 1.4.1)*
109
+ - Color MUST NOT be the sole differentiator between variants — icon shape and text label must also differ *(WCAG 1.4.1)*
110
+ - `role="alert"` causes immediate assertion by screen readers — only use for error and warning severity
111
+ - `role="status"` uses a polite live region — appropriate for info and success
112
+ - Dismiss button MUST have `aria-label` (e.g. `aria-label="Dismiss warning"`) *(WAI-ARIA APG)*
113
+ - Do NOT auto-dismiss alerts — that is Toast's job; alert stays until explicitly dismissed *(Carbon, Polaris)*
114
+
115
+ Cross-link: `reference/accessibility.md` — live-regions, color-contrast sections
116
+
117
+ ---
118
+
119
+ ## Motion
120
+
121
+ | Transition | Duration | Easing | Notes |
122
+ |------------|----------|--------|-------|
123
+ | Enter (height expand + fade) | 200ms | ease-out | Pushes content down; avoids position:fixed |
124
+ | Exit (height collapse + fade) | 150ms | ease-in | Content reflows up as alert closes |
125
+ | Icon entrance | 0ms | — | No animation on icon; immediate |
126
+
127
+ **BAN**: Slide-in from edge (reserve for Toast). Alert is inline — it should feel like content appearing, not a notification arriving.
128
+
129
+ Cross-link: `reference/motion.md` — `prefers-reduced-motion`: skip height animation, instant show/hide
130
+
131
+ ---
132
+
133
+ ## Do / Don't
134
+
135
+ ### Do
136
+ - Always include an icon matching the semantic variant *(WCAG 1.4.1 — color not sole differentiator)*
137
+ - Use `role="alert"` for warning/error (assertive) and `role="status"` for info/success (polite) *(WAI-ARIA APG)*
138
+ - Keep alert visible until the condition is resolved or user dismisses *(Carbon, Polaris)*
139
+ - Use full-width for page-level alerts; rounded inline for component-level *(Material 3, Carbon)*
140
+
141
+ ### Don't
142
+ - Don't auto-dismiss alerts — use Toast for transient messages *(Material 3, Carbon)*
143
+ - Don't use color alone to distinguish severity — always include an icon and text label *(WCAG 1.4.1)*
144
+ - Don't stack more than 2 alerts in the same section — consolidate into a single summary *(Polaris)*
145
+ - Don't use `role="alert"` for info/success — overly assertive announcements desensitize users *(WAI-ARIA APG)*
146
+
147
+ ---
148
+
149
+ ## Anti-patterns Cross-links
150
+
151
+ | Anti-pattern | Entry |
152
+ |--------------|-------|
153
+ | Color as sole variant differentiator | `reference/anti-patterns.md#ban-color-only` |
154
+ | role="alert" on info/success (over-announcing) | `reference/anti-patterns.md#ban-live-region` |
155
+
156
+ ---
157
+
158
+ ## Benchmark Citations
159
+
160
+ | Claim | Sources |
161
+ |-------|---------|
162
+ | Icon required; color not sole differentiator | WCAG 1.4.1; Material 3, Carbon, Polaris, Atlassian |
163
+ | role="alert" for error/warning; role="status" for info/success | WAI-ARIA APG, Carbon, Polaris |
164
+ | No auto-dismiss | Material 3, Carbon, Polaris, Atlassian |
165
+ | Full-width vs. rounded inline placement | Material 3, Carbon, Atlassian |
166
+ | Dismiss button requires aria-label | WAI-ARIA APG |
167
+
168
+ Full system URLs: `connections/design-corpora.md`
169
+
170
+ ---
171
+
172
+ ## Grep Signatures
173
+
174
+ ```bash
175
+ # Alert missing role attribute entirely
176
+ grep -rn 'alert\|Alert\|banner\|Banner\|notification' src/ | grep 'class=\|className=' | grep -v 'role='
177
+
178
+ # Error/warning alert using role="status" instead of role="alert"
179
+ grep -rn 'role="status"' src/ | grep -i 'error\|warning\|danger'
180
+
181
+ # Color-only differentiation — severity class with no icon reference
182
+ grep -rn 'alert--error\|alert--warning\|alert-error\|alert-warning' src/ | grep -v 'icon\|Icon\|svg\|SVG'
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Failing Example
188
+
189
+ ```html
190
+ <!-- BAD: alert using only color class — no icon, no role, no text variant label -->
191
+ <div class="alert alert--error">
192
+ Your session has expired. Please log in again.
193
+ </div>
194
+ ```
195
+
196
+ **Why it fails**: No `role="alert"` so screen readers do not announce the error message. Color class alone distinguishes severity — users with color blindness or high-contrast themes cannot distinguish this from a neutral message. No icon provides a shape-based cue.
197
+ **Grep detection**: `grep -rn 'class.*alert--error\|class.*alert-error' src/ | grep -v 'role='`
198
+ **Fix**: Add `role="alert"`, include an error icon with `aria-hidden="true"`, and ensure the variant is communicated through text or visually-hidden label in addition to color.
@@ -0,0 +1,202 @@
1
+ # Badge — Benchmark Spec
2
+
3
+ **Harvested from**: Material 3, Polaris, Carbon, Radix
4
+ **Wave**: 3 · **Category**: Feedback
5
+
6
+ ---
7
+
8
+ ## Purpose
9
+
10
+ A badge is a compact numeric counter or status indicator overlaid on or beside a parent element (icon, avatar, button) to communicate a count (unread messages, notification count) or status (online, offline, busy). It is purely decorative — the accessible count or status is surfaced through the parent element's `aria-label`. *(Material 3, Polaris, Carbon, Radix agree: badge is decorative; parent carries accessible state)*
11
+
12
+ ---
13
+
14
+ ## Anatomy
15
+
16
+ **Attached (overlay)**
17
+ ```
18
+ ┌──────────────────────┐
19
+ │ [icon/avatar] │
20
+ │ ┌──┐│
21
+ │ │ 3││ ← badge, position: absolute top-right
22
+ │ └──┘│
23
+ └──────────────────────┘
24
+ ↑ parent: aria-label="Messages, 3 unread"
25
+ ```
26
+
27
+ **Standalone**
28
+ ```
29
+ ┌──────┐
30
+ │ 99+ │ ← badge standalone (rare; decorative in a list)
31
+ └──────┘
32
+ ```
33
+
34
+ | Part | Required | Notes |
35
+ |------|----------|-------|
36
+ | Badge element | Yes | Pill or circle shape containing count or dot |
37
+ | Count / label text | Conditional | Present for count/icon variants; absent for dot |
38
+ | Parent element | Yes (for attached) | Carries `aria-label` with accessible count |
39
+ | Dot indicator | No | Status dot variant — no number, pure color/shape |
40
+
41
+ ---
42
+
43
+ ## Variants
44
+
45
+ | Variant | Description | Systems |
46
+ |---------|-------------|---------|
47
+ | Count | Numeric; shows integer up to 99, then "99+" | Material 3, Polaris, Carbon, Radix |
48
+ | Dot | Status indicator; no number; color/position only | Material 3, Polaris, Radix |
49
+ | Icon overlay | Small icon inside badge shape (rare) | Material 3 |
50
+
51
+ **Norm** (≥4/18 systems agree): count badges cap display at "99+"; zero count hidden by default; dot variant uses the same position/size slot as count but without text.
52
+ **Diverge**: Material 3 calls the dot variant "small badge" (8px, no content) vs. "large badge" (16px+, with number); Polaris uses "badge" exclusively for text status labels (not overlaid); Carbon uses "tag" for text labels, "notification badge" for counts. Radix provides a headless primitive suitable for all variants.
53
+
54
+ ---
55
+
56
+ ## States
57
+
58
+ | State | Trigger | Visual | ARIA |
59
+ |-------|---------|--------|------|
60
+ | default | count > 0 | Badge visible, pill shape | Parent `aria-label` updated |
61
+ | zero | count = 0 | Badge hidden (default); visible if `showZero` prop | Parent `aria-label` reflects 0 or omits count |
62
+ | max overflow | count > 99 | Renders "99+" | Parent `aria-label` says "99 or more unread" |
63
+ | dot status | status active | Dot visible; colored by status | Parent `aria-label` includes status text |
64
+
65
+ ---
66
+
67
+ ## Sizing & Spacing
68
+
69
+ | Variant | Min height | Min width | Font size | Notes |
70
+ |---------|-----------|-----------|-----------|-------|
71
+ | Count (sm) | 16px | 16px (= height) | 10px/500 | Single digit fills circle |
72
+ | Count (md) | 20px | 20px (= height) | 12px/500 | Two-digit + "99+" |
73
+ | Dot | 8px | 8px | — | No text; absolute top-right |
74
+
75
+ - **Shape**: pill when width > height; circle when width = height (single digit)
76
+ - **Position** (attached): `position: absolute; top: -4px; right: -4px` relative to parent
77
+ - **Min-width = height** (pill rule): ensures pill does not compress on 2-digit counts
78
+
79
+ **Norm**: 16–20px height; 10–12px font-size; min-width equals height *(Material 3, Polaris, Carbon)*.
80
+
81
+ ---
82
+
83
+ ## Typography
84
+
85
+ - Count text: 10–12px / 500 (medium weight) — legible at small scale
86
+ - No wrapping; never truncate count text; use "99+" cap instead
87
+ - Letter-spacing: 0 — tight spacing at 10–12px scale
88
+
89
+ Cross-link: `reference/typography.md` — small label scale (10–12px)
90
+
91
+ ---
92
+
93
+ ## Keyboard & Accessibility
94
+
95
+ > **WAI-ARIA role**: none (decorative); parent element carries accessible count via `aria-label`
96
+ > **Required attributes**: none on badge itself; `aria-label` on parent with count embedded
97
+
98
+ ### Keyboard Contract
99
+
100
+ *Quoted verbatim from WAI-ARIA APG — https://www.w3.org/WAI/ARIA/apg/ — W3C — 2024*
101
+
102
+ | Key | Action |
103
+ |-----|--------|
104
+ | (none) | Badge is non-interactive; no keyboard behavior |
105
+
106
+ Badge never receives focus. It is a purely visual overlay. All keyboard interaction is on the parent element.
107
+
108
+ ### Accessibility Rules
109
+
110
+ - Badge element itself MUST have `aria-hidden="true"` — screen readers should not announce the badge number separately; they read it as part of the parent's `aria-label`
111
+ - Parent element MUST have an `aria-label` that includes the count: `aria-label="Messages, 3 unread"` *(WAI-ARIA APG, Material 3)*
112
+ - Parent `aria-label` MUST be updated dynamically when count changes — use `aria-live` on the parent if the count changes while the page is rendered *(WCAG 4.1.3)*
113
+ - Zero badge: if badge is hidden at zero, remove it from DOM (or `display: none`) — do NOT leave it with empty text in the DOM *(Polaris)*
114
+ - Dot variant: parent `aria-label` MUST include the status: `aria-label="User profile, status: online"` *(Radix)*
115
+ - Never rely on badge color alone to communicate status — include text in parent `aria-label` *(WCAG 1.4.1)*
116
+
117
+ Cross-link: `reference/accessibility.md` — dynamic label updates, aria-live
118
+
119
+ ---
120
+
121
+ ## Motion
122
+
123
+ | Transition | Duration | Easing | Notes |
124
+ |------------|----------|--------|-------|
125
+ | Count increment (scale pulse) | 150ms | ease-out | Brief scale 1.2 → 1.0 on value change |
126
+ | Badge appear | 100ms | ease-out | Fade + scale from 0.5 |
127
+ | Badge disappear (at zero) | 80ms | ease-in | Fade + scale to 0 |
128
+
129
+ **BAN**: Continuous bounce animation on badge — implies urgency and is distracting; one-shot pulse on value change is acceptable.
130
+
131
+ Cross-link: `reference/motion.md` — scale-in/scale-out; `prefers-reduced-motion`: skip all badge animation
132
+
133
+ ---
134
+
135
+ ## Do / Don't
136
+
137
+ ### Do
138
+ - Include count in parent `aria-label`: `aria-label="Inbox, 5 unread messages"` *(WAI-ARIA APG)*
139
+ - Add `aria-hidden="true"` to the badge element itself *(WAI-ARIA APG)*
140
+ - Cap numeric display at "99+" and update parent label to "99 or more" *(Material 3, Carbon)*
141
+ - Hide badge when count is 0 by default; offer `showZero` prop for product preference *(Polaris, Radix)*
142
+
143
+ ### Don't
144
+ - Don't surface badge count only through color (dot badge without parent label) *(WCAG 1.4.1)*
145
+ - Don't put interactive content in a badge — it is always decorative *(Material 3, Carbon)*
146
+ - Don't animate badge continuously — one-shot pulse on value change only *(Polaris)*
147
+ - Don't use badge text as the only accessible notification — always update parent `aria-label` *(WAI-ARIA APG)*
148
+
149
+ ---
150
+
151
+ ## Anti-patterns Cross-links
152
+
153
+ | Anti-pattern | Entry |
154
+ |--------------|-------|
155
+ | Color as sole status differentiator | `reference/anti-patterns.md#ban-color-only` |
156
+ | Missing aria-label on parent with count | `reference/anti-patterns.md#ban-aria-label` |
157
+
158
+ ---
159
+
160
+ ## Benchmark Citations
161
+
162
+ | Claim | Sources |
163
+ |-------|---------|
164
+ | Badge is decorative; parent carries aria-label | Material 3, Polaris, Carbon, Radix |
165
+ | Count capped at "99+" | Material 3, Carbon, Radix |
166
+ | 16–20px height; min-width = height | Material 3, Polaris, Carbon |
167
+ | Zero badge hidden by default | Polaris, Radix |
168
+ | aria-hidden="true" on badge element | WAI-ARIA APG |
169
+ | Parent aria-label updated on count change | WCAG 4.1.3 |
170
+
171
+ Full system URLs: `connections/design-corpora.md`
172
+
173
+ ---
174
+
175
+ ## Grep Signatures
176
+
177
+ ```bash
178
+ # Badge count not surfaced in parent aria-label
179
+ grep -rn 'badge\|Badge' src/ | grep -v 'aria-label' | grep -v 'aria-hidden'
180
+
181
+ # Badge element missing aria-hidden
182
+ grep -rn 'class.*badge\|className.*badge' src/ | grep -v 'aria-hidden="true"'
183
+
184
+ # Parent with badge but no aria-label
185
+ grep -rn 'badge\|Badge' src/ -l | xargs grep -l 'button\|icon\|avatar' | xargs grep -L 'aria-label'
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Failing Example
191
+
192
+ ```html
193
+ <!-- BAD: badge count visible but parent has no aria-label for screen readers -->
194
+ <button class="icon-btn">
195
+ <svg aria-hidden="true"><!-- bell icon --></svg>
196
+ <span class="badge">3</span>
197
+ </button>
198
+ ```
199
+
200
+ **Why it fails**: Screen readers announce "button" with no mention of the count. The `<span class="badge">3</span>` may be announced as isolated "3" out of context, or the badge text is read before the button label, producing confusing output. The button has no accessible name describing its purpose or the notification count.
201
+ **Grep detection**: `grep -rn 'class.*badge' src/ | xargs grep -B2 -A2 'button\|icon' | grep -v 'aria-label'`
202
+ **Fix**: `<button class="icon-btn" aria-label="Notifications, 3 unread"><svg aria-hidden="true">…</svg><span class="badge" aria-hidden="true">3</span></button>`