--- name: accessibility description: Audit and improve web accessibility following WCAG 2.1 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: "1.0" --- # Accessibility (a11y) Comprehensive accessibility guidelines based on WCAG 2.1 and Lighthouse accessibility audits. Goal: make content usable by everyone, including people with disabilities. ## 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 */ :focus-visible { outline: 2px solid #005fcc; 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:** ```javascript // ❌ Only handles click element.addEventListener('click', handleAction); // ✅ Handles both click and keyboard element.addEventListener('click', handleAction); element.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleAction(); } }); ``` **No keyboard traps:** ```javascript // Modal focus management 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]; // Trap focus within modal 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(); } ``` ### Focus visible (2.4.7) ```css /* ❌ Never remove focus outlines */ *:focus { outline: none; } /* ✅ Use :focus-visible for keyboard-only focus */ :focus { outline: none; } :focus-visible { outline: 2px solid #005fcc; outline-offset: 2px; } /* ✅ Or custom focus styles */ button:focus-visible { box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5); } ``` ### Skip links (2.4.1) ```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; } ``` ### Timing (2.2) ```javascript // Allow users to extend time limits function showSessionWarning() { const modal = createModal({ title: 'Session Expiring', content: 'Your session will expire in 2 minutes.', actions: [ { label: 'Extend session', action: extendSession }, { label: 'Log out', action: logout } ], timeout: 120000 // 2 minutes to respond }); } ``` ### Motion (2.3) ```css /* Respect reduced motion preference */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } ``` --- ## Understandable ### Page language (3.1.1) ```html

The French word for hello is bonjour.

``` ### Consistent navigation (3.2.3) ```html ``` ### Form labels (3.3.2) ```html

Must be at least 8 characters with one number.

``` ### Error handling (3.3.1, 3.3.3) ```html
``` ```javascript // Focus first error on submit form.addEventListener('submit', (e) => { const firstError = form.querySelector('[aria-invalid="true"]'); if (firstError) { e.preventDefault(); firstError.focus(); // Announce error summary const errorSummary = document.getElementById('error-summary'); errorSummary.textContent = `${errors.length} errors found. Please fix them and try again.`; errorSummary.focus(); } }); ``` --- ## Robust ### Valid HTML (4.1.1) ```html
...
...
...
Click ``` ### ARIA usage (4.1.2) **Prefer native elements:** ```html
Click me
Option
``` **When ARIA is needed:** ```html
``` ### Live regions (4.1.3) ```html
``` ```javascript // Announce dynamic content changes function showNotification(message, type = 'polite') { const container = document.getElementById(`${type}-announcer`); container.textContent = ''; // Clear first requestAnimationFrame(() => { container.textContent = message; }); } ``` --- ## Testing checklist ### Automated testing ```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 ### Screen reader commands | Action | VoiceOver (Mac) | NVDA (Windows) | |--------|-----------------|----------------| | Start/Stop | ⌘ + F5 | Ctrl + Alt + N | | Next item | VO + → | ↓ | | Previous item | VO + ← | ↑ | | Activate | VO + Space | Enter | | Headings list | VO + U, then arrows | H / Shift + H | | Links list | VO + U | K / Shift + K | --- ## 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.1 Quick Reference](https://www.w3.org/WAI/WCAG21/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)