Building Accessible React Components
A deep dive into creating inclusive user interfaces that work for everyone.
Building Accessible React Components
Creating accessible web applications isn't just about following guidelines—it's about ensuring everyone can use what we build. In this article, we'll explore practical techniques for building inclusive React components.
Why Accessibility Matters
Web accessibility ensures that people with disabilities can perceive, understand, navigate, and interact with websites. This includes people who are:
- Visually impaired — using screen readers or magnification
- Hearing impaired — relying on captions and transcripts
- Motor impaired — using keyboards or alternative input devices
- Cognitively impaired — benefiting from clear navigation and content
Semantic HTML First
Before reaching for ARIA attributes, use semantic HTML elements:
// ❌ Bad - div with click handler
<div onClick={handleClick} className="button">
Click me
</div>
// ✅ Good - semantic button element
<button onClick={handleClick} className="button">
Click me
</button>Managing Focus
Focus management is crucial for keyboard navigation. Here's a custom hook for managing focus:
import { useRef, useEffect } from "react";
export function useFocusTrap(isActive: boolean) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isActive) return;
const container = containerRef.current;
if (!container) return;
const focusableElements = container.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
);
const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[
focusableElements.length - 1
] as HTMLElement;
firstElement?.focus();
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key !== "Tab") return;
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement?.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement?.focus();
}
};
container.addEventListener("keydown", handleKeyDown);
return () => container.removeEventListener("keydown", handleKeyDown);
}, [isActive]);
return containerRef;
}Accessible Form Components
Forms are where accessibility often falls short. Here's a properly accessible input component:
interface InputProps {
id: string;
label: string;
error?: string;
required?: boolean;
}
export function AccessibleInput({
id,
label,
error,
required,
...props
}: InputProps & React.InputHTMLAttributes<HTMLInputElement>) {
const errorId = `${id}-error`;
return (
<div className="field">
<label htmlFor={id}>
{label}
{required && <span aria-hidden="true"> *</span>}
</label>
<input
id={id}
aria-required={required}
aria-invalid={!!error}
aria-describedby={error ? errorId : undefined}
{...props}
/>
{error && (
<p id={errorId} role="alert" className="error">
{error}
</p>
)}
</div>
);
}Testing Accessibility
Use these tools to test your components:
| Tool | Purpose |
|---|---|
| axe DevTools | Automated accessibility testing |
| NVDA/VoiceOver | Screen reader testing |
| Keyboard only | Manual keyboard navigation |
| Color contrast | Check WCAG contrast ratios |
Conclusion
Building accessible components requires intentional effort, but the payoff is significant. Not only do you create better experiences for users with disabilities, but you also improve usability for everyone.
"The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect." — Tim Berners-Lee