Accessibility
Accessible React is mostly semantic HTML plus a handful of ARIA patterns and disciplined focus management.
React accessibility
EXAMPLE
// 1. Semantic HTML beats div + role
// Wrong
<div onClick={save} className='btn'>Save</div>
// Right
<button type='button' onClick={save}>Save</button>
// 2. Labelled inputs
<label htmlFor='email' className='block text-sm'>Email</label>
<input id='email' type='email' aria-describedby='email-help' />
<p id='email-help' className='text-xs text-gray-500'>We will never share it.</p>
// 3. Focus management on route change
import { useEffect, useRef } from 'react';
import { useLocation } from 'react-router-dom';
function PageTitle({ children }: { children: React.ReactNode }) {
const h1 = useRef<HTMLHeadingElement>(null);
const { pathname } = useLocation();
useEffect(() => { h1.current?.focus(); }, [pathname]);
return <h1 ref={h1} tabIndex={-1}>{children}</h1>;
}
// 4. Live regions for async status
function Status({ message }: { message: string }) {
return <div role='status' aria-live='polite'>{message}</div>;
}
// 5. Modal traps + restores focus
import { useEffect, useRef } from 'react';
function Modal({ open, onClose, children }) {
const ref = useRef<HTMLDivElement>(null);
const previousFocus = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!open) return;
previousFocus.current = document.activeElement as HTMLElement;
ref.current?.focus();
return () => previousFocus.current?.focus();
}, [open]);
if (!open) return null;
return (
<div role='dialog' aria-modal='true' ref={ref} tabIndex={-1}>
{children}
<button onClick={onClose}>Close</button>
</div>
);
}
// 6. Custom components - use react-aria or headless UI
// They give you focus order, ARIA wiring, and keyboard support for free.
// 7. Test it
import { render, screen } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
it('has no a11y violations', async () => {
const { container } = render(<Page />);
expect(await axe(container)).toHaveNoViolations();
});
// 8. Manual checks - the part axe cannot catch
// - Tab through the page; does it make sense?
// - Use a screen reader (VoiceOver, NVDA) for one page per release
// - Check colour contrast (4.5:1 for text, 3:1 for UI)
// - prefers-reduced-motion respected for transforms
Why it matters
Accessibility is engineering, not a checkbox. Semantic HTML covers 80 percent; ARIA + focus management covers the gap; axe + a screen reader covers regressions. Test a page weekly the way a keyboard-only user would.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
<button aria-label="Close" onClick={close}>×</button>
<input id="email" /><label htmlFor="email">Email</label>
Try it Yourself »
Discussion
Loading…