Accessibility Guide
Complete guide to making the AI Store application accessible.
WCAG Compliance
Level AA Compliance
The application aims for WCAG 2.1 Level AA compliance:
- ✅ Perceivable
- ✅ Operable
- ✅ Understandable
- ✅ Robust
Keyboard Navigation
Tab Order
Ensure logical tab order:
// Good: Logical order
<div>
<button>First</button>
<button>Second</button>
<button>Third</button>
</div>
Focus Management
import { trapFocus } from '@/lib/accessibility';
// Trap focus in modal
useEffect(() => {
if (isOpen) {
trapFocus(modalRef.current);
}
}, [isOpen]);
Skip Links
import SkipToMain from '@/components/SkipToMain';
// Automatically added to layout
<SkipToMain />
Keyboard Shortcuts
import { useKeyboardShortcut } from '@/hooks/useKeyboardShortcut';
useKeyboardShortcut({
key: 'Escape',
action: () => closeModal(),
description: 'Close modal',
});
Screen Readers
ARIA Labels
// Good: Descriptive labels
<button aria-label="Close modal">
<svg>...</svg>
</button>
// Good: Labeled inputs
<label htmlFor="email">Email</label>
<input id="email" type="email" />
ARIA Roles
// Good: Semantic roles
<nav role="navigation">
<ul role="menubar">
<li role="menuitem">Item</li>
</ul>
</nav>
ARIA States
// Good: State information
<button
aria-expanded={isOpen}
aria-controls="menu"
>
Menu
</button>
Live Regions
import { announceToScreenReader } from '@/lib/accessibility';
// Announce to screen readers
announceToScreenReader('Form submitted successfully', 'polite');
Semantic HTML
Use Semantic Elements
// Good: Semantic HTML
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Title</h1>
<p>Content</p>
</article>
</main>
<footer>
<p>Copyright</p>
</footer>
Headings Hierarchy
// Good: Proper hierarchy
<h1>Page Title</h1>
<h2>Section Title</h2>
<h3>Subsection Title</h3>
Color and Contrast
Contrast Ratios
- Normal text: 4.5:1 minimum
- Large text: 3:1 minimum
- UI components: 3:1 minimum
Color Independence
// Bad: Color only
<span style={{ color: 'red' }}>Error</span>
// Good: Color + icon/text
<span style={{ color: 'red' }}>
<span aria-label="Error">⚠️</span> Error message
</span>
Images
Alt Text
// Good: Descriptive alt text
<img src="chart.jpg" alt="Sales increased 25% in Q3 2024" />
// Decorative images
<img src="decoration.jpg" alt="" />
Next.js Image
import Image from 'next/image';
<Image
src="/image.jpg"
alt="Descriptive alt text"
width={800}
height={600}
/>
Forms
Labels
// Good: Associated labels
<label htmlFor="email">Email Address</label>
<input id="email" type="email" />
// Good: Implicit labels
<label>
Email Address
<input type="email" />
</label>
Error Messages
// Good: Accessible errors
<input
id="email"
type="email"
aria-invalid={hasError}
aria-describedby="email-error"
/>
{hasError && (
<div id="email-error" role="alert">
Please enter a valid email address
</div>
)}
Required Fields
// Good: Clear indication
<label htmlFor="name">
Name <span aria-label="required">*</span>
</label>
<input id="name" required aria-required="true" />
Focus Indicators
Visible Focus
/* Good: Visible focus */
*:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
Focus Styles
// Ensure interactive elements have focus styles
<button className="focus:outline-2 focus:outline-primary">
Click me
</button>
Motion and Animation
Reduced Motion
import { useAccessibility } from '@/hooks/useAccessibility';
function MyComponent() {
const { reducedMotion } = useAccessibility();
return (
<div
style={{
animation: reducedMotion ? 'none' : 'fadeIn 0.3s',
}}
>
Content
</div>
);
}
CSS Media Query
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Testing
Automated Testing
# Install axe-core
npm install --save-dev @axe-core/react
# Run accessibility tests
npm run test:a11y
Manual Testing
-
Keyboard Navigation
- Tab through all interactive elements
- Verify focus indicators
- Test all keyboard shortcuts
-
Screen Reader Testing
- Test with NVDA/JAWS/VoiceOver
- Verify all content is announced
- Check ARIA labels
-
Color Contrast
- Use contrast checker tools
- Verify all text meets WCAG AA
Components
Accessible Modal
<Modal
isOpen={isOpen}
onClose={handleClose}
title="Modal Title"
aria-labelledby="modal-title"
>
<h2 id="modal-title">Modal Title</h2>
Content
</Modal>
Accessible Dropdown
<Dropdown
trigger={<button aria-haspopup="true">Menu</button>}
items={items}
role="menu"
/>
Accessible Form
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
required
aria-required="true"
aria-invalid={hasError}
aria-describedby={hasError ? "email-error" : undefined}
/>
{hasError && (
<div id="email-error" role="alert">
{errorMessage}
</div>
)}
</form>
Best Practices
1. Use Semantic HTML
// Good
<button>Click me</button>
<nav>Navigation</nav>
<main>Content</main>
// Bad
<div onClick={handleClick}>Click me</div>
<div>Navigation</div>
<div>Content</div>
2. Provide Text Alternatives
// Good
<button aria-label="Close dialog">
<svg>...</svg>
</button>
// Bad
<button>
<svg>...</svg>
</button>
3. Ensure Keyboard Access
// Good: Keyboard accessible
<button onClick={handleClick}>Action</button>
// Bad: Mouse only
<div onClick={handleClick}>Action</div>
4. Test with Screen Readers
- Test all interactions
- Verify announcements
- Check navigation
5. Maintain Focus
- Don't trap focus unnecessarily
- Return focus after modal closes
- Manage focus in dynamic content
Resources
Checklist
- [ ] Keyboard navigation works
- [ ] Screen reader compatible
- [ ] Color contrast meets WCAG AA
- [ ] All images have alt text
- [ ] Forms are accessible
- [ ] Focus indicators visible
- [ ] Reduced motion supported
- [ ] ARIA labels used correctly
- [ ] Semantic HTML used
- [ ] Tested with screen readers