Accessibility in Single Page Applications (SPAs)
Single page applications built with React (or any SPA framework) need extra care. Because the page does not reload during navigation, you have to manage focus yourself and tell assistive technologies about dynamic changes.
Here are the best practices for React accessibility.
1. Managing Focus on Route Changes
When a user clicks a link in a traditional multi-page site, the browser loads the new page and moves focus to the top. In React Router, the URL changes and components re-render, but focus often stays on the clicked link or gets lost entirely.
Solution: When a route changes, programmatically move focus to a meaningful element on the new page, typically the main <h1> heading or a visually hidden "skip" container. You can use a useEffect hook linked to the route path and a ref.
2. Using React Fragments
Avoid adding unnecessary <div> elements just to satisfy React's single-parent requirement, as this can break HTML semantics (like placing a <div> inside a <ul> or <table>). Use React Fragments (<React.Fragment> or the shorthand <></>) to group elements without adding extra nodes to the DOM.
3. Semantic HTML Over Custom Divs
It's tempting in React to build everything out of styled <div>s and <span>s with onClick handlers. This is an accessibility anti-pattern.
- Use
<button>for actions. - Use
<a href="...">for navigation. - If you must use a
<div>as a button, you must addrole="button",tabIndex={0}, and handle bothonClickandonKeyDown(Enter and Space keys).
4. Live Regions for Dynamic Content
When content updates dynamically without a page reload (e.g., a notification toast, a form submission error, or search results loading), screen readers won't announce it automatically.
Use ARIA live regions (aria-live="polite" or aria-live="assertive") to instruct screen readers to announce these changes.
<div aria-live="polite" className="sr-only">
{errorMessage}
</div>
5. Accessible Forms in React
Ensure every <input> has a corresponding <label>. In React, use the htmlFor prop instead of for.
<label htmlFor="emailInput">Email Address</label>
<input id="emailInput" type="email" />