Building Accessible React Components from Scratch
Learn the principles and patterns for building truly accessible React components that work for all users.
AN
Aisha Nakamura
2 min read·December 12, 2024
Building Accessible React Components from Scratch
Accessibility isn't an afterthought—it's a fundamental aspect of good design. Here's how to build React components that work for everyone.
Why Accessibility Matters
- 15%+ of the world has some form of disability
- Legal requirements in many jurisdictions
- Better UX for everyone: Accessibility improvements benefit all users
- SEO benefits: Semantic HTML helps search engines
Core Principles
1. Semantic HTML
Use the right element for the job:
// Bad
<div onClick={handleClick}>Click me</div>
// Good
<button onClick={handleClick}>Click me</button>
2. Keyboard Navigation
Everything clickable must be keyboard accessible:
function Button({ onClick, children }) {
return (
<button
onClick={onClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
onClick(e);
}
}}
>
{children}
</button>
);
}
3. ARIA When Needed
Use ARIA attributes to enhance semantics:
| Attribute | Purpose |
|---|---|
| aria-label | Provides accessible name |
| aria-describedby | Links to description |
| aria-expanded | Indicates toggle state |
| aria-hidden | Hides from assistive tech |
Building Common Components
Accessible Modal
function Modal({ isOpen, onClose, title, children }) {
const modalRef = useRef(null);
useEffect(() => {
if (isOpen) {
modalRef.current?.focus();
document.body.style.overflow = 'hidden';
}
return () => {
document.body.style.overflow = '';
};
}, [isOpen]);
if (!isOpen) return null;
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
ref={modalRef}
tabIndex={-1}
>
<h2 id="modal-title">{title}</h2>
{children}
<button onClick={onClose}>Close</button>
</div>
);
}
Accessible Dropdown
Key requirements:
- Arrow key navigation
- Escape to close
- Focus management
- Screen reader announcements
Testing Accessibility
Automated Testing
// Using jest-axe
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('button has no accessibility violations', async () => {
const { container } = render(<Button>Click me</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Manual Testing
- Keyboard only: Navigate without a mouse
- Screen reader: Test with VoiceOver/NVDA
- Zoom: Test at 200% zoom
- Color contrast: Check with contrast checkers
Resources
- WCAG 2.1 Guidelines
- WAI-ARIA Practices
- Inclusive Components by Heydon Pickering
- A11y Project
What accessibility challenges have you faced? Share below!
AN
Written by
Aisha Nakamura
Design systems lead at Figma. Sharing insights on UI/UX, accessibility, and building cohesive design languages.
2,877 views
1
Share
Responses (1)
Bookmarked! The modal example is exactly what I've been looking for. Proper focus management is so often overlooked and it makes such a difference for keyboard users.