Accessibility Standards¶
Accessibility Standards¶
Semantic HTML¶
Use proper HTML elements:
// Good - semantic HTML
<nav aria-label="Main navigation">
<ul>
<li><a href="/home">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<main>
<article>
<header>
<h1>Article Title</h1>
</header>
<section>...</section>
</article>
</main>
// Bad - divs everywhere
<div className="nav">
<div><div onClick={...}>Home</div></div>
</div>
ARIA Labels¶
Add ARIA attributes where semantic HTML isn't enough:
// Icon buttons need labels
<button aria-label="Close dialog">
<XIcon />
</button>
// Form errors
<input
aria-invalid={!!error}
aria-describedby={error ? 'email-error' : undefined}
/>
{error && <p id="email-error" role="alert">{error}</p>}
// Loading states
<div aria-busy={isLoading} aria-live="polite">
{isLoading ? <Spinner /> : content}
</div>
Keyboard Navigation¶
Ensure all interactive elements are keyboard accessible:
// Custom interactive elements need keyboard handlers
function Card({ onClick }: { onClick: () => void }) {
return (
<div
role="button"
tabIndex={0}
onClick={onClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClick();
}
}}
>
...
</div>
);
}
// Focus management in modals
function Modal({ isOpen, onClose }: ModalProps) {
const closeButtonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (isOpen) {
closeButtonRef.current?.focus();
}
}, [isOpen]);
// Trap focus within modal...
}
Testing Accessibility¶
Use getByRole in tests:
// Good - query by role
const submitButton = screen.getByRole('button', { name: /submit/i });
const emailInput = screen.getByRole('textbox', { name: /email/i });
const errorMessage = screen.getByRole('alert');
// Avoid - query by test ID
const submitButton = screen.getByTestId('submit-btn'); // Last resort