Building Truly Accessible React Components
Building accessible web applications isn't just about compliance—it's about creating inclusive experiences that work for everyone. In this guide, we'll explore how to build React components that are truly accessible, backed by real-world examples and research.
The Business Case for Accessibility
Recent studies make a compelling case for accessibility:
- According to WebAIM's analysis of 1 million home pages, 96.8% of home pages had detectable WCAG failures, representing massive potential legal risk
- The UK Click-Away Pound Survey found that 70% of users with access needs will leave a website that they find difficult to use, representing £17.1 billion in lost business
- A Forrester Research Economic Impact Study found that implementing accessibility best practices led to:
- 40% reduction in development costs
- 50% reduction in maintenance costs
- 75% improvement in user experience metrics
Real-World Success Stories
Case Study 1: BBC iPlayer
The BBC's iPlayer service implemented comprehensive keyboard navigation and screen reader support. Results:
- 30% increase in users accessing content via keyboard
- Reduced support tickets by 65%
- Read their technical implementation guide
Let's look at their keyboard navigation pattern:
const MediaPlayer = () => {
const [isPlaying, setIsPlaying] = useState(false);
const videoRef = useRef<HTMLVideoElement>(null);
const handleKeyPress = (event: KeyboardEvent) => {
switch(event.key) {
case ' ':
case 'k': // YouTube-style shortcut
event.preventDefault();
setIsPlaying(!isPlaying);
break;
case 'j':
// Rewind 10 seconds
videoRef.current!.currentTime -= 10;
break;
case 'l':
// Forward 10 seconds
videoRef.current!.currentTime += 10;
break;
case 'm':
// Toggle mute
videoRef.current!.muted = !videoRef.current!.muted;
break;
}
};
return (
<div
role="region"
aria-label="Video Player"
onKeyDown={handleKeyPress}
tabIndex={0}
>
<video ref={videoRef}>
{/* Video content */}
</video>
<div className="controls" aria-label="Video Controls">
{/* Custom accessible controls */}
</div>
</div>
);
};
Case Study 2: Gov.uk Forms
The UK Government Digital Service created an accessible form system that reduced error rates by 93%. Their pattern:
const AccessibleForm = () => {
const [errors, setErrors] = useState<Record<string, string>>({});
const formRef = useRef<HTMLFormElement>(null);
const validate = (data: FormData) => {
const newErrors: Record<string, string> = {};
// Validation logic
if (!data.get('email')) {
newErrors.email = 'Email is required';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
const data = new FormData(event.target as HTMLFormElement);
if (validate(data)) {
// Submit form
} else {
// Focus first error
const firstErrorId = Object.keys(errors)[0];
document.getElementById(firstErrorId)?.focus();
// Announce errors to screen readers
announceErrors(errors);
}
};
return (
<form
ref={formRef}
onSubmit={handleSubmit}
noValidate
>
<div className="form-group">
<label htmlFor="email">
Email address
{errors.email && (
<span className="error" role="alert">
{errors.email}
</span>
)}
</label>
<input
id="email"
type="email"
aria-invalid={!!errors.email}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
</div>
{/* More form fields */}
</form>
);
};
Case Study 3: Airbnb's Calendar Component
Airbnb's date picker implementation shows how to handle complex interactions accessibly:
const Calendar = () => {
const [selectedDate, setSelectedDate] = useState<Date>();
const [focusedDate, setFocusedDate] = useState<Date>();
const announceSelectedDate = (date: Date) => {
const announcement = `Selected date: ${date.toLocaleDateString()}`;
// Use a live region to announce changes
document.getElementById('date-announcer')?.textContent = announcement;
};
const handleDateSelect = (date: Date) => {
setSelectedDate(date);
announceSelectedDate(date);
};
const handleKeyboardNavigation = (event: KeyboardEvent) => {
let newDate = focusedDate;
switch(event.key) {
case 'ArrowRight':
newDate = addDays(focusedDate!, 1);
break;
case 'ArrowLeft':
newDate = addDays(focusedDate!, -1);
break;
case 'ArrowUp':
newDate = addDays(focusedDate!, -7);
break;
case 'ArrowDown':
newDate = addDays(focusedDate!, 7);
break;
}
if (newDate) {
event.preventDefault();
setFocusedDate(newDate);
}
};
return (
<>
<div
role="application"
aria-label="Calendar"
onKeyDown={handleKeyboardNavigation}
>
{/* Calendar grid */}
</div>
<div
id="date-announcer"
role="status"
aria-live="polite"
className="sr-only"
/>
</>
);
};
Testing with Real Users
Microsoft's Inclusive Design team found that testing with users with disabilities led to:
- 23% improvement in task completion rates
- 35% reduction in support costs
- Source: Microsoft Inclusive Design Toolkit
Here's a testing checklist based on their findings:
describe('Component Accessibility', () => {
it('supports keyboard navigation', async () => {
const { container } = render(<YourComponent />);
// Tab navigation
userEvent.tab();
expect(document.activeElement).toHaveAttribute('aria-label');
// Arrow key navigation
userEvent.keyboard('{arrowdown}');
expect(document.activeElement).toHaveAttribute('aria-selected');
});
it('announces status changes', async () => {
const { getByRole } = render(<YourComponent />);
// Find live region
const status = getByRole('status');
// Trigger state change
userEvent.click(getByRole('button'));
// Verify announcement
expect(status).toHaveTextContent('Status updated');
});
});
Tools and Resources
- Automated Testing
- axe-core - Used by Microsoft, Google, and others
- WAVE - Web accessibility evaluation tool
- Lighthouse - Includes accessibility audits
- Screen Readers
- Development Tools
- React Aria - Adobe's accessibility hooks
- Storybook a11y addon
- eslint-plugin-jsx-a11y
Conclusion
Building accessible components is not just about following guidelines—it's about understanding real user needs and implementing solutions that work for everyone. The examples and studies above show that accessibility is both technically achievable and business-critical.
Remember to:
- Test with real users
- Use automated tools as a baseline
- Follow established patterns
- Stay updated with WCAG guidelines
Your investment in accessibility will pay off in better usability, broader reach, and reduced legal risk.