diff --git a/.agents/skills/accessibility/SKILL.md b/.agents/skills/accessibility/SKILL.md new file mode 100644 index 000000000..22a244430 --- /dev/null +++ b/.agents/skills/accessibility/SKILL.md @@ -0,0 +1,464 @@ +--- +name: accessibility +description: Audit and improve web accessibility following WCAG 2.2 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible". +license: MIT +metadata: + author: web-quality-skills + version: "2.0" +--- + +# Accessibility (a11y) + +Comprehensive accessibility guidelines based on WCAG 2.2 and Lighthouse accessibility audits. Goal: make content usable by everyone, including people with disabilities. + +## Evidence-led audit workflow + +When a rendered page is available: + +1. Run a live Lighthouse Accessibility audit when that capability is available; with Chrome DevTools MCP, use `lighthouse_audit`. Use mobile navigation mode for a general public page or snapshot mode when reloading would lose authenticated or user-created state. +2. Use failed audit nodes to localize the relevant component or template instead of searching the whole repository for generic patterns. +3. Inspect a rendered accessibility-tree snapshot for names, roles, states, landmarks, and heading structure; with Chrome DevTools MCP, use `take_snapshot`. Exercise the affected flow with the keyboard. +4. Fix the source, then re-run the same audit and manual interaction. + +If the live tools are unavailable, use Lighthouse CLI or axe for automated coverage and complete the same manual checks. Automated tools detect only a subset of accessibility barriers: a score of 100 is not WCAG conformance, and a low score does not replace issue-level evidence. + +## WCAG Principles: POUR + +| Principle | Description | +|-----------|-------------| +| **P**erceivable | Content can be perceived through different senses | +| **O**perable | Interface can be operated by all users | +| **U**nderstandable | Content and interface are understandable | +| **R**obust | Content works with assistive technologies | + +## Conformance levels + +| Level | Requirement | Target | +|-------|-------------|--------| +| **A** | Minimum accessibility | Must pass | +| **AA** | Standard compliance | Should pass (legal requirement in many jurisdictions) | +| **AAA** | Enhanced accessibility | Nice to have | + +--- + +## Perceivable + +### Text alternatives (1.1) + +**Images require alt text:** +```html + + + + +Bar chart showing 40% increase in Q3 sales + + + + + +
+ 2024 market trends infographic +
+ +
+
+``` + +**Icon buttons need accessible names:** +```html + + + + + + + + +``` + +**Visually hidden class:** +```css +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} +``` + +### Color contrast (1.4.3, 1.4.6) + +| Text Size | AA minimum | AAA enhanced | +|-----------|------------|--------------| +| Normal text (< 18px / < 14px bold) | 4.5:1 | 7:1 | +| Large text (≥ 18px / ≥ 14px bold) | 3:1 | 4.5:1 | +| UI components & graphics | 3:1 | 3:1 | + +```css +/* ❌ Low contrast (2.5:1) */ +.low-contrast { + color: #999; + background: #fff; +} + +/* ✅ Sufficient contrast (7:1) */ +.high-contrast { + color: #333; + background: #fff; +} + +/* ✅ Focus states need contrast too (3:1 against background, WCAG 1.4.11) */ +:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; +} +``` + +**Don't rely on color alone:** +```html + + + + + +
+ + + + Please enter a valid email address + +
+``` + +### Media alternatives (1.2) + +```html + + + + + +
+ Transcript +

Full transcript text...

+
+``` + +--- + +## Operable + +### Keyboard accessible (2.1) + +**All functionality must be keyboard accessible.** Prefer native interactive elements — ` +``` + +```javascript +// ✅ When you MUST use a non-interactive element (e.g. div with role="button"), +// make it focusable AND handle keyboard activation. Do NOT add this to a native +// + +``` + +--- + +## Robust + +### ARIA usage (4.1.2) + +**Prefer native elements:** +```html + +
Click me
+ + + + + +
Option
+ + + +``` + +**When ARIA is needed,** use the correct roles and states. See the [ARIA tabs pattern](references/A11Y-PATTERNS.md#aria-tabs) for a complete tablist example. + +### Live regions (4.1.3) + +Use `aria-live` regions to announce dynamic content changes without moving focus. See the [live regions pattern](references/A11Y-PATTERNS.md#live-regions-and-notifications) for markup and a `showNotification()` helper. + +--- + +## Testing checklist + +### Automated testing + +Prefer a live Lighthouse audit that returns failing rendered nodes directly to the agent. With Chrome DevTools MCP, this is `lighthouse_audit`. Otherwise: + +```bash +# Lighthouse accessibility audit +npx lighthouse https://example.com --only-categories=accessibility + +# axe-core +npm install @axe-core/cli -g +axe https://example.com +``` + +### Manual testing + +- [ ] **Keyboard navigation:** Tab through entire page, use Enter/Space to activate +- [ ] **Screen reader:** Test with VoiceOver (Mac), NVDA (Windows), or TalkBack (Android) +- [ ] **Zoom:** Content usable at 200% zoom +- [ ] **High contrast:** Test with Windows High Contrast Mode +- [ ] **Reduced motion:** Test with `prefers-reduced-motion: reduce` +- [ ] **Focus order:** Logical and follows visual order +- [ ] **Target size:** Interactive elements meet 24×24px minimum + +See the [screen reader commands reference](references/A11Y-PATTERNS.md#screen-reader-commands) for VoiceOver and NVDA shortcuts. + +--- + +## Common issues by impact + +### Critical (fix immediately) +1. Missing form labels +2. Missing image alt text +3. Insufficient color contrast +4. Keyboard traps +5. No focus indicators + +### Serious (fix before launch) +1. Missing page language +2. Missing heading structure +3. Non-descriptive link text +4. Auto-playing media +5. Missing skip links + +### Moderate (fix soon) +1. Missing ARIA labels on icons +2. Inconsistent navigation +3. Missing error identification +4. Timing without controls +5. Missing landmark regions + +## References + +- [WCAG 2.2 Quick Reference](https://www.w3.org/WAI/WCAG22/quickref/) +- [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/) +- [Deque axe Rules](https://dequeuniversity.com/rules/axe/) +- [Web Quality Audit](../web-quality-audit/SKILL.md) +- [WCAG criteria reference](references/WCAG.md) +- [Accessibility code patterns](references/A11Y-PATTERNS.md) diff --git a/.agents/skills/accessibility/references/A11Y-PATTERNS.md b/.agents/skills/accessibility/references/A11Y-PATTERNS.md new file mode 100644 index 000000000..6d500efb3 --- /dev/null +++ b/.agents/skills/accessibility/references/A11Y-PATTERNS.md @@ -0,0 +1,233 @@ +# Accessibility Code Patterns + +Practical, copy-paste-ready patterns for common accessibility requirements. Each pattern is self-contained and linked from the main [SKILL.md](../SKILL.md). + +--- + +## Modal focus trap + +Trap keyboard focus inside a modal dialog so Tab/Shift+Tab cycle through its focusable elements and Escape closes it. + +```javascript +function openModal(modal) { + const focusableElements = modal.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + modal.addEventListener('keydown', (e) => { + if (e.key === 'Tab') { + if (e.shiftKey && document.activeElement === firstElement) { + e.preventDefault(); + lastElement.focus(); + } else if (!e.shiftKey && document.activeElement === lastElement) { + e.preventDefault(); + firstElement.focus(); + } + } + if (e.key === 'Escape') { + closeModal(); + } + }); + + firstElement.focus(); +} +``` + +The native `` element handles focus trapping automatically—prefer it when browser support allows. + +--- + +## Skip link + +Allows keyboard users to bypass repetitive navigation and jump straight to main content. + +```html + + +
+
+ +
+ +``` + +```css +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: #000; + color: #fff; + padding: 8px 16px; + z-index: 100; +} + +.skip-link:focus { + top: 0; +} +``` + +--- + +## Error handling + +Announce errors to screen readers and focus the first invalid field on submit. + +```html +
+
+ + + +
+
+``` + +```javascript +form.addEventListener('submit', (e) => { + const firstError = form.querySelector('[aria-invalid="true"]'); + if (firstError) { + e.preventDefault(); + firstError.focus(); + + const errorSummary = document.getElementById('error-summary'); + errorSummary.textContent = + `${errors.length} errors found. Please fix them and try again.`; + errorSummary.focus(); + } +}); +``` + +--- + +## Form labels + +Every input needs an associated label—either explicit (`for`/`id`) or implicit (wrapping `