Accessibility (a11y) in React
In this page:
Prefer Semantic HTML Elements
A real <button> element comes with built-in keyboard support (Enter/Space triggers it), correct screen-reader announcements, and proper focus behavior automatically. A <div onClick={...}> styled to look like a button has none of this by default — you'd have to painstakingly reimplement it all yourself.
Note: Before reaching for a div/span with a click handler, check if there's already a semantic HTML element (button, a, nav, main, header) that fits the purpose.
Warning: A clickable div with no keyboard handling is completely unusable for anyone navigating by keyboard alone, including many screen-reader users.
Example: Prefer Semantic HTML Elements
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
return (
<div>
<button onClick={() => alert("Clicked")}>Good: real button, keyboard-accessible</button>
<br/>
<div onClick={() => alert("Clicked")} style={{cursor: "pointer", color: "blue"}}>Bad: div pretending to be a button</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Providing Text Alternatives
Every meaningful image needs an alt attribute describing its content for users who can't see it, and icon-only buttons need an accessible label (via aria-label) since there's no visible text for a screen reader to announce.
Note: For purely decorative images with no informational content, use alt="" (empty, not omitted) so screen readers correctly skip announcing them.
Warning: An icon-only button with no aria-label announces as just button to a screen reader user, with no indication of what it actually does.
Example: Providing Text Alternatives
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
return (
<div>
<img src="chart.png" alt="Sales increased 20% in Q3" />
<button aria-label="Close dialog">✕</button>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Keeping Focus Visible and Logical
Keyboard users rely on a visible focus outline to know which element is currently selected, and expect Tab to move through interactive elements in a sensible, predictable order matching the visual layout. Removing the focus outline entirely, without a clear replacement, leaves keyboard users with no way to tell where they are.
Note: If a default focus outline doesn't match your design, replace it with an equally visible custom style — don't just remove it.
Warning: outline: none with no visible alternative makes keyboard navigation effectively invisible, a serious accessibility regression.
Example: Keeping Focus Visible and Logical
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
return (
<div>
<button style={{ outline: "2px solid transparent" }}>Bad: focus indicator hidden entirely</button>
<button style={{ outlineOffset: "2px" }}>Good: keeps a visible focus outline</button>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Using a <div> with an onClick handler instead of a real <button>, losing built-in keyboard support and screen-reader semantics for free.
- Forgetting alt text on meaningful images, leaving screen-reader users with no idea what's shown.
- Removing focus outlines with CSS (outline: none) without providing any visible alternative, making keyboard navigation invisible.
- Accessibility (a11y) means building UI usable by people with a wide range of abilities, including screen-reader and keyboard-only users.
- Semantic HTML elements (button, nav, main) provide built-in accessibility behavior that a generic div can't replicate for free.
- ARIA attributes (aria-label, aria-hidden) fill gaps when semantic HTML alone isn't sufficient.
- Visible focus indicators and logical tab order matter for anyone navigating by keyboard, not just assistive-technology users.
Semantic HTML/ARIA support is universal across modern browsers; no specific React version requirement.
Chapter Quiz — Complete all 14 topics to unlock
0/14 topics done
Complete these topics first:
- Error Boundaries
- React Portals
- Modals using Portals (practical use)
- React Suspense
- Code Splitting with React.lazy
- Introduction to Server Components
- Introduction to Next.js (server-side React)
- Using React with TypeScript
- Scalable Folder Architecture
- Common React Design Patterns
- Component Documentation with Storybook
- Accessibility (a11y) in React
- i18n with react-i18next
- React Security Best Practices