picasso-skill 1.2.0 → 1.3.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/README.md +23 -7
- package/agents/picasso.md +305 -2
- package/package.json +1 -1
- package/skills/picasso/references/accessibility-wcag.md +245 -0
- package/skills/picasso/references/anti-patterns.md +138 -49
- package/skills/picasso/references/color-and-contrast.md +251 -2
- package/skills/picasso/references/conversion-design.md +193 -0
- package/skills/picasso/references/data-visualization.md +226 -0
- package/skills/picasso/references/modern-css-performance.md +361 -0
- package/skills/picasso/references/performance-optimization.md +746 -0
- package/skills/picasso/references/style-presets.md +502 -0
- package/skills/picasso/references/typography.md +206 -2
- package/skills/picasso/references/ux-psychology.md +235 -0
- package/skills/picasso/references/ux-writing.md +513 -0
- package/skills/picasso/references/accessibility.md +0 -172
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Upload `skills/picasso/SKILL.md` as a Custom Skill in Claude.ai settings. Upload
|
|
|
56
56
|
|
|
57
57
|
| | Skill | Agent |
|
|
58
58
|
|---|---|---|
|
|
59
|
-
| **What it is** | Static knowledge base (
|
|
59
|
+
| **What it is** | Static knowledge base (20 reference files) | Autonomous design engineer |
|
|
60
60
|
| **How it works** | Loaded into context when designing | Runs as a sub-process with its own tools |
|
|
61
61
|
| **When it runs** | When you ask for design help | Proactively after any frontend code change |
|
|
62
62
|
| **Can audit code** | Only when you ask | Automatically |
|
|
@@ -71,22 +71,32 @@ Upload `skills/picasso/SKILL.md` as a Custom Skill in Claude.ai settings. Upload
|
|
|
71
71
|
skills/picasso/
|
|
72
72
|
SKILL.md # Main skill file (knowledge base)
|
|
73
73
|
references/
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
anti-patterns.md # AI slop fingerprint + what NOT to do
|
|
75
|
+
typography.md # Font pairing, type scales, variable fonts, 12 curated pairings
|
|
76
|
+
color-and-contrast.md # OKLCH, 10 curated palettes, P3 wide gamut, dark mode
|
|
76
77
|
spatial-design.md # Spacing scales, grids, visual hierarchy
|
|
77
78
|
motion-and-animation.md # Easing, staggering, text morphing, reduced motion
|
|
78
79
|
interaction-design.md # Forms, focus, loading, empty states, errors
|
|
79
|
-
responsive-design.md # Mobile-first,
|
|
80
|
+
responsive-design.md # Mobile-first, dvh/svh, container queries, print
|
|
80
81
|
sensory-design.md # UI sounds, haptic feedback, multi-sensory
|
|
81
82
|
react-patterns.md # React 19, Tailwind v4, Server Actions, dark mode
|
|
82
|
-
|
|
83
|
+
accessibility-wcag.md # Full ARIA patterns, WCAG 2.2, SPA focus management
|
|
84
|
+
modern-css-performance.md # :has(), scroll animations, view transitions, @layer
|
|
85
|
+
performance-optimization.md # Core Web Vitals, 45 React/Next.js perf patterns
|
|
86
|
+
ux-writing.md # Button labels, error templates, microcopy, banned words
|
|
87
|
+
ux-psychology.md # Gestalt, cognitive laws, scanning patterns
|
|
88
|
+
conversion-design.md # Landing pages, CTAs, pricing, onboarding
|
|
89
|
+
data-visualization.md # Charts, dashboards, data palettes, Tufte principles
|
|
90
|
+
style-presets.md # 22 curated visual presets with colors + fonts
|
|
83
91
|
design-system.md # DESIGN.md generation, theming, tokens (OKLCH)
|
|
84
92
|
generative-art.md # p5.js, SVG, Canvas 2D, noise, seeded randomness
|
|
85
93
|
component-patterns.md # Standard naming, taxonomy, state matrix
|
|
86
|
-
accessibility.md # ARIA, keyboard nav, screen readers, WCAG 2.2
|
|
87
94
|
|
|
88
95
|
agents/
|
|
89
96
|
picasso.md # Autonomous design auditor agent
|
|
97
|
+
|
|
98
|
+
templates/
|
|
99
|
+
picasso-config.md # .picasso.md project config template
|
|
90
100
|
```
|
|
91
101
|
|
|
92
102
|
## Agent Commands
|
|
@@ -107,7 +117,13 @@ The Picasso agent responds to these commands:
|
|
|
107
117
|
| `/theme` | Generate or apply a theme |
|
|
108
118
|
| `/stitch` | Generate a complete DESIGN.md from the current codebase |
|
|
109
119
|
| `/harden` | Add error handling, loading states, empty states |
|
|
110
|
-
| `/a11y` | Accessibility
|
|
120
|
+
| `/a11y` | Accessibility audit: axe-core + pa11y + Lighthouse |
|
|
121
|
+
| `/perf` | Lighthouse performance audit with Core Web Vitals thresholds |
|
|
122
|
+
| `/visual-diff` | Screenshot desktop + mobile in light/dark, analyze visually |
|
|
123
|
+
| `/consistency` | Multi-page consistency check across all routes |
|
|
124
|
+
| `/lint-design` | Find hardcoded colors, spacing, fonts, z-index chaos |
|
|
125
|
+
| `/install-hooks` | Generate git pre-commit hook for design checks |
|
|
126
|
+
| `/ci-setup` | Generate GitHub Actions workflow for PR design review |
|
|
111
127
|
|
|
112
128
|
## Agent Audit Checklist
|
|
113
129
|
|
package/agents/picasso.md
CHANGED
|
@@ -41,7 +41,13 @@ Before judging anything, understand what you're working with.
|
|
|
41
41
|
1. **Identify changed files** -- run `git diff --name-only` and `git diff --staged --name-only` to find modified frontend files (.tsx, .jsx, .css, .html, .svelte, .vue, .astro)
|
|
42
42
|
2. **Read the files** -- read every changed frontend file in full. Do not review code you haven't read.
|
|
43
43
|
3. **Find the design system** -- search for `DESIGN.md`, `tailwind.config.*`, `theme.ts`, `tokens.css`, `globals.css`, or CSS variable definitions. If a design system exists, all findings must be measured against it.
|
|
44
|
-
4. **
|
|
44
|
+
4. **Load project design config** -- search for `.picasso.md` in the project root (or locate it with `glob **/.picasso.md`). If found, parse it and treat its values as the project's declared design preferences:
|
|
45
|
+
- **Typography overrides** -- if the config declares a font (e.g., Inter, Roboto), do NOT flag it as AI-slop. The project has intentionally chosen it.
|
|
46
|
+
- **Color overrides** -- if the config declares a primary accent or neutral tint, validate usage against those values instead of Picasso defaults.
|
|
47
|
+
- **Design settings** -- honor `DESIGN_VARIANCE`, `MOTION_INTENSITY`, and `VISUAL_DENSITY` when calibrating the severity and scope of suggestions.
|
|
48
|
+
- **Constraints** -- treat every listed constraint as a hard requirement that overrides other Picasso recommendations (e.g., if "No animations" is listed, skip all motion suggestions).
|
|
49
|
+
- If `.picasso.md` is **not found**, proceed with Picasso defaults and note in the report that no project config was detected. You can generate one with the config template at `templates/picasso-config.md`.
|
|
50
|
+
5. **Check for existing patterns** -- grep for common component imports (shadcn, radix, headless-ui, chakra, mantine) to understand the component library in use.
|
|
45
51
|
|
|
46
52
|
## Phase 2: Design Audit
|
|
47
53
|
|
|
@@ -310,7 +316,304 @@ When the user invokes these commands, execute the corresponding workflow:
|
|
|
310
316
|
| `/theme` | Generate or apply a theme via DESIGN.md |
|
|
311
317
|
| `/stitch` | Generate a complete DESIGN.md from the current codebase |
|
|
312
318
|
| `/harden` | Add error handling, loading states, empty states, edge case handling |
|
|
313
|
-
| `/a11y` | Accessibility-only audit: run axe-
|
|
319
|
+
| `/a11y` | Accessibility-only audit: run axe-cli, pa11y, and Lighthouse accessibility category with JSON output parsing; check ARIA, validate contrast, test keyboard nav |
|
|
320
|
+
| `/perf` | Performance audit: run Lighthouse CLI, extract Core Web Vitals (LCP, CLS, INP/TBT), report with pass/fail thresholds |
|
|
321
|
+
| `/visual-diff` | Visual regression: take desktop + mobile screenshots in light and dark mode, analyze for AI-slop indicators |
|
|
322
|
+
| `/consistency` | Multi-page consistency check: discover routes, run checks across all pages, produce cross-page comparison table |
|
|
323
|
+
| `/lint-design` | Design token linting: find hardcoded colors, inconsistent spacing, non-standard fonts, z-index chaos, transition:all |
|
|
324
|
+
| `/install-hooks` | Generate a git pre-commit hook that runs fast grep-based design checks (no server needed) |
|
|
325
|
+
| `/ci-setup` | Generate a GitHub Actions workflow for PR design review: a11y, perf, screenshots, PR comment |
|
|
326
|
+
|
|
327
|
+
## Advanced Automation Commands
|
|
328
|
+
|
|
329
|
+
### /perf -- Performance Audit
|
|
330
|
+
|
|
331
|
+
Run Lighthouse CLI, extract Core Web Vitals (LCP, CLS, INP/TBT), report scores with pass/fail thresholds:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
npx lighthouse http://localhost:3000 --only-categories=performance --output=json --output-path=/tmp/lh-perf.json --chrome-flags="--headless --no-sandbox" --quiet
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Parse the JSON output to extract these metrics with thresholds:
|
|
338
|
+
|
|
339
|
+
| Metric | Pass | Needs Work | Fail |
|
|
340
|
+
|---|---|---|---|
|
|
341
|
+
| Performance Score | >= 90 | 50-89 | < 50 |
|
|
342
|
+
| FCP (First Contentful Paint) | < 1.8s | 1.8-3.0s | > 3.0s |
|
|
343
|
+
| LCP (Largest Contentful Paint) | < 2.5s | 2.5-4.0s | > 4.0s |
|
|
344
|
+
| CLS (Cumulative Layout Shift) | < 0.1 | 0.1-0.25 | > 0.25 |
|
|
345
|
+
| TBT (Total Blocking Time) | < 200ms | 200-600ms | > 600ms |
|
|
346
|
+
| SI (Speed Index) | < 3.4s | 3.4-5.8s | > 5.8s |
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
# Parse results from JSON
|
|
350
|
+
node -e "
|
|
351
|
+
const r = require('/tmp/lh-perf.json');
|
|
352
|
+
const a = r.audits;
|
|
353
|
+
console.log('Performance Score:', Math.round(r.categories.performance.score * 100));
|
|
354
|
+
console.log('FCP:', a['first-contentful-paint'].displayValue);
|
|
355
|
+
console.log('LCP:', a['largest-contentful-paint'].displayValue);
|
|
356
|
+
console.log('CLS:', a['cumulative-layout-shift'].displayValue);
|
|
357
|
+
console.log('TBT:', a['total-blocking-time'].displayValue);
|
|
358
|
+
console.log('SI:', a['speed-index'].displayValue);
|
|
359
|
+
"
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### /visual-diff -- Visual Regression
|
|
363
|
+
|
|
364
|
+
Take screenshots at desktop (1440x900) and mobile (375x812), both light and dark mode. Use Playwright screenshot commands:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# Desktop - Light mode
|
|
368
|
+
npx playwright screenshot http://localhost:3000 /tmp/picasso-desktop-light.png --viewport-size=1440,900 2>/dev/null
|
|
369
|
+
|
|
370
|
+
# Desktop - Dark mode (inject prefers-color-scheme)
|
|
371
|
+
npx playwright screenshot http://localhost:3000 /tmp/picasso-desktop-dark.png --viewport-size=1440,900 --color-scheme=dark 2>/dev/null
|
|
372
|
+
|
|
373
|
+
# Mobile - Light mode
|
|
374
|
+
npx playwright screenshot http://localhost:3000 /tmp/picasso-mobile-light.png --viewport-size=375,812 2>/dev/null
|
|
375
|
+
|
|
376
|
+
# Mobile - Dark mode
|
|
377
|
+
npx playwright screenshot http://localhost:3000 /tmp/picasso-mobile-dark.png --viewport-size=375,812 --color-scheme=dark 2>/dev/null
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Analyze all four screenshots visually for:
|
|
381
|
+
- AI-slop indicators (generic gradients, everything centered, uniform card grids)
|
|
382
|
+
- Light/dark mode consistency (same hierarchy, no lost contrast, no invisible elements)
|
|
383
|
+
- Mobile responsiveness (no overflow, readable text, adequate touch targets)
|
|
384
|
+
- Visual regression from previous state (if baseline screenshots exist)
|
|
385
|
+
|
|
386
|
+
### /consistency -- Multi-Page Consistency Check
|
|
387
|
+
|
|
388
|
+
Discover routes (from file-system routing or user input), run the same checks across all pages, produce a cross-page comparison table:
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
# Discover routes from Next.js app directory
|
|
392
|
+
find src/app -name "page.tsx" -o -name "page.jsx" 2>/dev/null | sed 's|src/app||;s|/page\.\(tsx\|jsx\)||;s|^$|/|'
|
|
393
|
+
|
|
394
|
+
# Or from pages directory
|
|
395
|
+
find src/pages -name "*.tsx" -o -name "*.jsx" 2>/dev/null | sed 's|src/pages||;s|\.\(tsx\|jsx\)||;s|/index$|/|'
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
For each discovered route:
|
|
399
|
+
1. Take a screenshot
|
|
400
|
+
2. Extract font families used (`grep -rn 'font-family\|fontFamily'`)
|
|
401
|
+
3. Extract color values used
|
|
402
|
+
4. Extract spacing patterns
|
|
403
|
+
5. Check for shared component usage
|
|
404
|
+
|
|
405
|
+
Output a cross-page comparison table:
|
|
406
|
+
|
|
407
|
+
```
|
|
408
|
+
| Page | Font Families | Primary Colors | Spacing Base | Shared Components |
|
|
409
|
+
|----------|---------------|----------------|--------------|-------------------|
|
|
410
|
+
| / | Geist, mono | oklch(...) | 4px scale | Header, Footer |
|
|
411
|
+
| /about | Geist, mono | oklch(...) | 4px scale | Header, Footer |
|
|
412
|
+
| /pricing | Geist, serif | #3b82f6 (!) | mixed (!) | Header only (!) |
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Flag inconsistencies with `(!)` markers.
|
|
416
|
+
|
|
417
|
+
### /lint-design -- Design Token Linting
|
|
418
|
+
|
|
419
|
+
Run Stylelint + grep-based checks to find design system violations:
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
# 1. Find hardcoded colors that should be tokens
|
|
423
|
+
grep -rn '#[0-9a-fA-F]\{3,8\}' --include="*.tsx" --include="*.jsx" --include="*.css" | grep -v 'node_modules\|\.git\|\.next' | head -30
|
|
424
|
+
|
|
425
|
+
# 2. Find inconsistent spacing values (non-4px-multiple)
|
|
426
|
+
grep -rn 'padding\|margin\|gap' --include="*.css" --include="*.tsx" | grep -oP '\d+px' | sort | uniq -c | sort -rn
|
|
427
|
+
|
|
428
|
+
# 3. Find non-standard font stacks
|
|
429
|
+
grep -rn 'font-family\|fontFamily' --include="*.css" --include="*.tsx" --include="*.jsx" | grep -v 'node_modules' | head -20
|
|
430
|
+
|
|
431
|
+
# 4. Find z-index chaos (values not from a defined scale)
|
|
432
|
+
grep -rn 'z-index\|zIndex' --include="*.css" --include="*.tsx" --include="*.jsx" | grep -v 'node_modules' | head -20
|
|
433
|
+
|
|
434
|
+
# 5. Find transition:all (anti-pattern)
|
|
435
|
+
grep -rn 'transition:\s*all\|transition-property:\s*all' --include="*.css" --include="*.tsx" --include="*.jsx" | grep -v 'node_modules'
|
|
436
|
+
|
|
437
|
+
# 6. Run Stylelint if available
|
|
438
|
+
npx stylelint "**/*.css" --formatter=json 2>/dev/null || true
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Report findings grouped by category with severity and suggested token replacements.
|
|
442
|
+
|
|
443
|
+
### /install-hooks -- Git Pre-commit Hook
|
|
444
|
+
|
|
445
|
+
Generate a `.git/hooks/pre-commit` script that runs fast design checks (grep-based, no server needed):
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
cat > .git/hooks/pre-commit << 'HOOK'
|
|
449
|
+
#!/usr/bin/env bash
|
|
450
|
+
set -e
|
|
451
|
+
|
|
452
|
+
STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(tsx|jsx|css|html|svelte|vue)$' || true)
|
|
453
|
+
[ -z "$STAGED" ] && exit 0
|
|
454
|
+
|
|
455
|
+
ERRORS=0
|
|
456
|
+
|
|
457
|
+
echo "Running Picasso pre-commit checks..."
|
|
458
|
+
|
|
459
|
+
# 1. transition:all detection
|
|
460
|
+
if echo "$STAGED" | xargs grep -l 'transition:\s*all' 2>/dev/null; then
|
|
461
|
+
echo "ERROR: transition:all found. Specify properties explicitly."
|
|
462
|
+
ERRORS=$((ERRORS + 1))
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
# 2. Pure black (#000) detection
|
|
466
|
+
if echo "$STAGED" | xargs grep -l '#000000\|#000[^0-9a-fA-F]' 2>/dev/null; then
|
|
467
|
+
echo "ERROR: Pure black (#000) found. Use tinted near-black instead."
|
|
468
|
+
ERRORS=$((ERRORS + 1))
|
|
469
|
+
fi
|
|
470
|
+
|
|
471
|
+
# 3. outline:none detection (without focus-visible replacement)
|
|
472
|
+
if echo "$STAGED" | xargs grep -l 'outline:\s*none\|outline:\s*0[^.]' 2>/dev/null; then
|
|
473
|
+
echo "WARNING: outline:none found. Ensure :focus-visible has a replacement."
|
|
474
|
+
ERRORS=$((ERRORS + 1))
|
|
475
|
+
fi
|
|
476
|
+
|
|
477
|
+
# 4. Missing alt text detection
|
|
478
|
+
if echo "$STAGED" | xargs grep -l '<img' 2>/dev/null | xargs grep -L 'alt=' 2>/dev/null; then
|
|
479
|
+
echo "ERROR: <img> tags without alt attribute found."
|
|
480
|
+
ERRORS=$((ERRORS + 1))
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
if [ "$ERRORS" -gt 0 ]; then
|
|
484
|
+
echo ""
|
|
485
|
+
echo "Picasso found $ERRORS design issue(s). Fix them before committing."
|
|
486
|
+
exit 1
|
|
487
|
+
fi
|
|
488
|
+
|
|
489
|
+
echo "Picasso pre-commit checks passed."
|
|
490
|
+
exit 0
|
|
491
|
+
HOOK
|
|
492
|
+
chmod +x .git/hooks/pre-commit
|
|
493
|
+
echo "Pre-commit hook installed."
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### /ci-setup -- GitHub Actions Workflow
|
|
497
|
+
|
|
498
|
+
Generate a `.github/workflows/picasso-review.yml` that runs on PRs touching frontend files:
|
|
499
|
+
|
|
500
|
+
```yaml
|
|
501
|
+
name: Picasso Design Review
|
|
502
|
+
|
|
503
|
+
on:
|
|
504
|
+
pull_request:
|
|
505
|
+
paths:
|
|
506
|
+
- '**/*.tsx'
|
|
507
|
+
- '**/*.jsx'
|
|
508
|
+
- '**/*.css'
|
|
509
|
+
- '**/*.html'
|
|
510
|
+
- '**/*.svelte'
|
|
511
|
+
- '**/*.vue'
|
|
512
|
+
|
|
513
|
+
jobs:
|
|
514
|
+
picasso-review:
|
|
515
|
+
runs-on: ubuntu-latest
|
|
516
|
+
steps:
|
|
517
|
+
- uses: actions/checkout@v4
|
|
518
|
+
|
|
519
|
+
- uses: actions/setup-node@v4
|
|
520
|
+
with:
|
|
521
|
+
node-version: '20'
|
|
522
|
+
cache: 'npm'
|
|
523
|
+
|
|
524
|
+
- run: npm ci
|
|
525
|
+
|
|
526
|
+
- name: Start dev server
|
|
527
|
+
run: npm run dev &
|
|
528
|
+
env:
|
|
529
|
+
PORT: 3000
|
|
530
|
+
|
|
531
|
+
- name: Wait for server
|
|
532
|
+
run: npx wait-on http://localhost:3000 --timeout 60000
|
|
533
|
+
|
|
534
|
+
- name: Accessibility audit (axe-cli)
|
|
535
|
+
run: npx axe-cli http://localhost:3000 --exit --save /tmp/axe-results.json || true
|
|
536
|
+
|
|
537
|
+
- name: Accessibility audit (pa11y)
|
|
538
|
+
run: npx pa11y http://localhost:3000 --reporter json > /tmp/pa11y-results.json || true
|
|
539
|
+
|
|
540
|
+
- name: Lighthouse accessibility
|
|
541
|
+
run: |
|
|
542
|
+
npx lighthouse http://localhost:3000 --only-categories=accessibility --output=json --output-path=/tmp/lh-a11y.json --chrome-flags="--headless --no-sandbox" --quiet || true
|
|
543
|
+
|
|
544
|
+
- name: Lighthouse performance
|
|
545
|
+
run: |
|
|
546
|
+
npx lighthouse http://localhost:3000 --only-categories=performance --output=json --output-path=/tmp/lh-perf.json --chrome-flags="--headless --no-sandbox" --quiet || true
|
|
547
|
+
|
|
548
|
+
- name: Take screenshots
|
|
549
|
+
run: |
|
|
550
|
+
npx playwright install chromium --with-deps
|
|
551
|
+
npx playwright screenshot http://localhost:3000 /tmp/picasso-desktop.png --viewport-size=1440,900
|
|
552
|
+
npx playwright screenshot http://localhost:3000 /tmp/picasso-mobile.png --viewport-size=375,812
|
|
553
|
+
|
|
554
|
+
- name: Parse scores
|
|
555
|
+
id: scores
|
|
556
|
+
run: |
|
|
557
|
+
PERF=$(node -e "const r=require('/tmp/lh-perf.json');console.log(Math.round(r.categories.performance.score*100))" 2>/dev/null || echo "N/A")
|
|
558
|
+
A11Y=$(node -e "const r=require('/tmp/lh-a11y.json');console.log(Math.round(r.categories.accessibility.score*100))" 2>/dev/null || echo "N/A")
|
|
559
|
+
echo "perf=$PERF" >> $GITHUB_OUTPUT
|
|
560
|
+
echo "a11y=$A11Y" >> $GITHUB_OUTPUT
|
|
561
|
+
|
|
562
|
+
- name: Upload artifacts
|
|
563
|
+
uses: actions/upload-artifact@v4
|
|
564
|
+
with:
|
|
565
|
+
name: picasso-results
|
|
566
|
+
path: /tmp/picasso-*.png
|
|
567
|
+
|
|
568
|
+
- name: Post PR comment
|
|
569
|
+
uses: actions/github-script@v7
|
|
570
|
+
with:
|
|
571
|
+
script: |
|
|
572
|
+
const perf = '${{ steps.scores.outputs.perf }}';
|
|
573
|
+
const a11y = '${{ steps.scores.outputs.a11y }}';
|
|
574
|
+
const body = `## Picasso Design Review\n\n| Metric | Score |\n|---|---|\n| Performance | ${perf}/100 |\n| Accessibility | ${a11y}/100 |\n\nScreenshots uploaded as workflow artifacts.`;
|
|
575
|
+
github.rest.issues.createComment({
|
|
576
|
+
issue_number: context.issue.number,
|
|
577
|
+
owner: context.repo.owner,
|
|
578
|
+
repo: context.repo.repo,
|
|
579
|
+
body
|
|
580
|
+
});
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### /a11y -- Accessibility Audit (Enhanced)
|
|
584
|
+
|
|
585
|
+
Run all three accessibility tools with JSON output parsing:
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
# 1. axe-cli -- WCAG 2.1 AA violations
|
|
589
|
+
npx axe-cli http://localhost:3000 --exit --save /tmp/axe-results.json 2>/dev/null
|
|
590
|
+
node -e "
|
|
591
|
+
const r = require('/tmp/axe-results.json');
|
|
592
|
+
const v = r[0]?.violations || [];
|
|
593
|
+
console.log('axe-cli: ' + v.length + ' violations');
|
|
594
|
+
v.forEach(v => console.log(' [' + v.impact + '] ' + v.id + ': ' + v.description + ' (' + v.nodes.length + ' nodes)'));
|
|
595
|
+
"
|
|
596
|
+
|
|
597
|
+
# 2. pa11y -- HTML_CodeSniffer + WCAG 2.1 AA
|
|
598
|
+
npx pa11y http://localhost:3000 --reporter json > /tmp/pa11y-results.json 2>/dev/null
|
|
599
|
+
node -e "
|
|
600
|
+
const r = require('/tmp/pa11y-results.json');
|
|
601
|
+
console.log('pa11y: ' + r.length + ' issues');
|
|
602
|
+
r.forEach(i => console.log(' [' + i.type + '] ' + i.code + ': ' + i.message));
|
|
603
|
+
"
|
|
604
|
+
|
|
605
|
+
# 3. Lighthouse accessibility category
|
|
606
|
+
npx lighthouse http://localhost:3000 --only-categories=accessibility --output=json --output-path=/tmp/lh-a11y.json --chrome-flags="--headless --no-sandbox" --quiet
|
|
607
|
+
node -e "
|
|
608
|
+
const r = require('/tmp/lh-a11y.json');
|
|
609
|
+
const score = Math.round(r.categories.accessibility.score * 100);
|
|
610
|
+
console.log('Lighthouse a11y score: ' + score + '/100');
|
|
611
|
+
const failed = Object.values(r.audits).filter(a => a.score === 0);
|
|
612
|
+
failed.forEach(a => console.log(' FAIL: ' + a.id + ' - ' + a.title));
|
|
613
|
+
"
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
Combine results from all three tools, deduplicate overlapping findings, and report with severity levels.
|
|
314
617
|
|
|
315
618
|
## Rules
|
|
316
619
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Accessibility & WCAG 2.2 Reference
|
|
2
|
+
|
|
3
|
+
## 1. ARIA Patterns Catalog
|
|
4
|
+
|
|
5
|
+
### Dialog / Modal
|
|
6
|
+
- `role="dialog"`, `aria-modal="true"`, `aria-labelledby` (title)
|
|
7
|
+
- Tab/Shift+Tab cycle within dialog (focus trap), Escape closes
|
|
8
|
+
- On open: focus first focusable element. On close: return focus to trigger.
|
|
9
|
+
|
|
10
|
+
```html
|
|
11
|
+
<div role="dialog" aria-modal="true" aria-labelledby="dlg-title">
|
|
12
|
+
<h2 id="dlg-title">Confirm Delete</h2>
|
|
13
|
+
<p>This action cannot be undone.</p>
|
|
14
|
+
<button>Cancel</button>
|
|
15
|
+
<button>Delete</button>
|
|
16
|
+
</div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Tabs
|
|
20
|
+
- `tablist` > `tab` + `tabpanel`
|
|
21
|
+
- `aria-selected="true|false"` on each tab, `aria-controls` linking tab to panel
|
|
22
|
+
- Left/Right arrows between tabs, Home/End to first/last
|
|
23
|
+
|
|
24
|
+
### Accordion
|
|
25
|
+
- Heading elements containing `<button>` triggers
|
|
26
|
+
- `aria-expanded="true|false"` on button, `aria-controls` pointing to content
|
|
27
|
+
- Enter/Space toggles section
|
|
28
|
+
|
|
29
|
+
### Combobox / Autocomplete
|
|
30
|
+
- `role="combobox"` on input, popup uses `listbox`
|
|
31
|
+
- `aria-expanded`, `aria-controls`, `aria-activedescendant`, `aria-autocomplete`
|
|
32
|
+
- Down Arrow opens/navigates popup, Escape closes, Enter selects
|
|
33
|
+
|
|
34
|
+
### Menu / Menubar
|
|
35
|
+
- `menu`/`menubar` > `menuitem`, `menuitemcheckbox`, `menuitemradio`
|
|
36
|
+
- `aria-haspopup`, `aria-expanded` on submenu triggers
|
|
37
|
+
- Arrow keys navigate, Enter/Space activates, Escape closes submenu
|
|
38
|
+
|
|
39
|
+
### Listbox
|
|
40
|
+
- `listbox` > `option`
|
|
41
|
+
- `aria-selected`, `aria-multiselectable` for multi-select
|
|
42
|
+
- Up/Down arrows, Home/End, type-ahead character navigation
|
|
43
|
+
|
|
44
|
+
### Tree View
|
|
45
|
+
- `tree` > `treeitem` (nested groups use `group` role)
|
|
46
|
+
- `aria-expanded` on parent nodes, `aria-level`, `aria-setsize`, `aria-posinset`
|
|
47
|
+
- Right expands/enters child, Left collapses/moves to parent
|
|
48
|
+
|
|
49
|
+
### Toolbar
|
|
50
|
+
- `toolbar` on container, `aria-orientation`
|
|
51
|
+
- Arrow keys between controls (roving tabindex), Tab moves out entirely
|
|
52
|
+
|
|
53
|
+
### Feed
|
|
54
|
+
- `feed` > `article` on each entry
|
|
55
|
+
- `aria-busy` while loading, `aria-setsize`/`aria-posinset` on articles
|
|
56
|
+
- Page Down/Up between articles
|
|
57
|
+
|
|
58
|
+
### Alert / Alert Dialog
|
|
59
|
+
- `role="alert"` (non-modal): implicitly `aria-live="assertive"`. No focus change.
|
|
60
|
+
- `role="alertdialog"` (modal): follows dialog focus trap pattern.
|
|
61
|
+
|
|
62
|
+
### Breadcrumb
|
|
63
|
+
- `<nav aria-label="Breadcrumb">` with ordered list
|
|
64
|
+
- `aria-current="page"` on current page link
|
|
65
|
+
|
|
66
|
+
### Disclosure
|
|
67
|
+
- `<button aria-expanded="false" aria-controls="content-id">` toggles content
|
|
68
|
+
- Enter/Space toggles expansion
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 2. Focus Management for SPAs
|
|
73
|
+
|
|
74
|
+
### Route Change Announcements
|
|
75
|
+
```html
|
|
76
|
+
<div aria-live="polite" class="sr-only" id="route-announcer"></div>
|
|
77
|
+
```
|
|
78
|
+
Update textContent on route change: "Products page loaded".
|
|
79
|
+
|
|
80
|
+
### Focus Restoration
|
|
81
|
+
Move focus to `<h1>` of new view (add `tabindex="-1"`) or main content landmark. On modal close, restore focus to trigger element.
|
|
82
|
+
|
|
83
|
+
### Skip Links
|
|
84
|
+
```html
|
|
85
|
+
<a href="#main" class="skip-link">Skip to main content</a>
|
|
86
|
+
<main id="main" tabindex="-1">...</main>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Focus Trapping
|
|
90
|
+
Contain Tab/Shift+Tab within overlays. Use `inert` attribute on background content (modern browsers) or manage via JS. On last focusable element, Tab wraps to first.
|
|
91
|
+
|
|
92
|
+
### Roving Tabindex
|
|
93
|
+
Only one child has `tabindex="0"` at a time; all others `tabindex="-1"`. On arrow key, swap values and `.focus()`. Alternative: `aria-activedescendant` on container.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 3. Accessible Forms
|
|
98
|
+
|
|
99
|
+
### Error Handling
|
|
100
|
+
```html
|
|
101
|
+
<input id="email" aria-invalid="true" aria-describedby="email-err" aria-required="true">
|
|
102
|
+
<span id="email-err" role="alert">Please enter a valid email address.</span>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
- `aria-invalid="true"` on fields with errors.
|
|
106
|
+
- `aria-describedby` linking to error message.
|
|
107
|
+
- `role="alert"` for immediate screen reader announcement.
|
|
108
|
+
- On submit, move focus to first invalid field.
|
|
109
|
+
|
|
110
|
+
### Required Fields
|
|
111
|
+
Use both `required` (native) and `aria-required="true"`. Pair with visible asterisk + legend.
|
|
112
|
+
|
|
113
|
+
### Field Descriptions
|
|
114
|
+
```html
|
|
115
|
+
<label for="pw">Password</label>
|
|
116
|
+
<input id="pw" type="password" aria-describedby="pw-hint">
|
|
117
|
+
<p id="pw-hint">Must be at least 8 characters with one number.</p>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Autocomplete Attributes
|
|
121
|
+
Use `autocomplete` values: `name`, `email`, `tel`, `street-address`, `postal-code`, `cc-number`, etc.
|
|
122
|
+
|
|
123
|
+
### Group Labeling
|
|
124
|
+
```html
|
|
125
|
+
<fieldset>
|
|
126
|
+
<legend>Payment Method</legend>
|
|
127
|
+
<label><input type="radio" name="pay" value="card"> Credit Card</label>
|
|
128
|
+
<label><input type="radio" name="pay" value="paypal"> PayPal</label>
|
|
129
|
+
</fieldset>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Accessible Date Pickers
|
|
133
|
+
Prefer native `<input type="date">`. For custom: `role="grid"` calendar, arrow key navigation, Enter to select, Escape to close, label each cell with full date.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 4. WCAG 2.2 New Criteria
|
|
138
|
+
|
|
139
|
+
### Target Size Minimum (2.5.8 -- Level AA)
|
|
140
|
+
All interactive targets must be at least **24x24 CSS pixels**.
|
|
141
|
+
|
|
142
|
+
```css
|
|
143
|
+
button, a, input, select, [role="button"] {
|
|
144
|
+
min-width: 24px;
|
|
145
|
+
min-height: 24px;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Dragging Alternatives (2.5.7 -- Level AA)
|
|
150
|
+
Every drag-and-drop operation must have a single-pointer alternative (click/tap). Sortable lists must also support "Move Up"/"Move Down" buttons.
|
|
151
|
+
|
|
152
|
+
### Focus Appearance (2.4.11 -- Level AA)
|
|
153
|
+
Focus indicators: minimum **2px perimeter outline** with **3:1 contrast ratio** between focused and unfocused states.
|
|
154
|
+
|
|
155
|
+
```css
|
|
156
|
+
:focus-visible {
|
|
157
|
+
outline: 2px solid #005fcc;
|
|
158
|
+
outline-offset: 2px;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Consistent Help (3.2.6 -- Level A)
|
|
163
|
+
Help mechanisms must appear in the **same relative order** across all pages.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 5. Screen Reader Dynamic Content
|
|
168
|
+
|
|
169
|
+
### aria-live Regions
|
|
170
|
+
```html
|
|
171
|
+
<div aria-live="polite" aria-atomic="true">3 results found</div>
|
|
172
|
+
<div aria-live="assertive">Session expiring in 30 seconds</div>
|
|
173
|
+
```
|
|
174
|
+
- `polite`: announced after current speech.
|
|
175
|
+
- `assertive`: interrupts (use sparingly).
|
|
176
|
+
- `aria-atomic="true"`: reads entire region, not just changed text.
|
|
177
|
+
|
|
178
|
+
### Status Messages (WCAG 4.1.3)
|
|
179
|
+
```html
|
|
180
|
+
<div role="status">File uploaded successfully.</div>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Loading States
|
|
184
|
+
```html
|
|
185
|
+
<div role="status" aria-live="polite">Loading results...</div>
|
|
186
|
+
<table aria-busy="true">...</table>
|
|
187
|
+
```
|
|
188
|
+
Set `aria-busy="true"` during update, `false` when complete.
|
|
189
|
+
|
|
190
|
+
### Progress Updates
|
|
191
|
+
```html
|
|
192
|
+
<div role="progressbar" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100"
|
|
193
|
+
aria-label="Upload progress">65%</div>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 6. Accessible Data Tables
|
|
199
|
+
|
|
200
|
+
### Complex Headers
|
|
201
|
+
```html
|
|
202
|
+
<th id="q1" scope="col">Q1</th>
|
|
203
|
+
<th id="revenue" scope="row">Revenue</th>
|
|
204
|
+
<td headers="q1 revenue">$1.2M</td>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Sortable Columns
|
|
208
|
+
```html
|
|
209
|
+
<th aria-sort="ascending" scope="col">
|
|
210
|
+
<button>Name <span aria-hidden="true">↑</span></button>
|
|
211
|
+
</th>
|
|
212
|
+
```
|
|
213
|
+
Values: `ascending`, `descending`, `none`. Only one column sorted at a time.
|
|
214
|
+
|
|
215
|
+
### Expandable Rows
|
|
216
|
+
Parent row: `aria-expanded="true|false"`. Use `aria-level`, `aria-setsize`, `aria-posinset` for treegrid.
|
|
217
|
+
|
|
218
|
+
### Responsive Tables
|
|
219
|
+
- **Reflow:** Transform cells into stacked blocks with `data-label` + CSS `::before` for headers.
|
|
220
|
+
- **Scroll:** `tabindex="0"`, `role="region"`, `aria-label="Scrollable table"`.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 7. Accessible Drag and Drop
|
|
225
|
+
|
|
226
|
+
### Keyboard Alternatives (WCAG 2.5.7)
|
|
227
|
+
```html
|
|
228
|
+
<li aria-roledescription="sortable item">
|
|
229
|
+
Item A
|
|
230
|
+
<button aria-label="Move Item A up">Up</button>
|
|
231
|
+
<button aria-label="Move Item A down">Down</button>
|
|
232
|
+
</li>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Live Region Announcements
|
|
236
|
+
```html
|
|
237
|
+
<div aria-live="assertive" class="sr-only" id="dnd-status"></div>
|
|
238
|
+
```
|
|
239
|
+
- On grab: "Grabbed Item A. Current position 2 of 5."
|
|
240
|
+
- On move: "Item A moved to position 3 of 5."
|
|
241
|
+
- On drop: "Item A dropped at position 3 of 5."
|
|
242
|
+
- On cancel: "Reorder cancelled. Item A returned to position 2."
|
|
243
|
+
|
|
244
|
+
### Modern Pattern
|
|
245
|
+
`aria-grabbed`/`aria-dropeffect` are deprecated. Use `aria-roledescription="draggable"`, `aria-pressed`/`aria-selected` for state, and live regions for announcements. Space/Enter to grab/drop, arrow keys to reposition, Escape to cancel.
|