// โ A div pretending to be a button gives screen readers and keyboard users nothing to work with
<div onClick={handleClick}>Submit</div>
// โ
A real button is focusable, keyboard-operable, and announced correctly by screen readers automatically
<button onClick={handleClick}>Submit</button>The single highest-impact accessibility practice is simply using the correct native HTML element for the job โ <button> for actions, <nav> for navigation, <main> for primary content โ rather than generic <div>s with click handlers attached. Browsers and assistive technology already understand these elements' roles for free.
function LoginForm() {
return (
<form>
<label htmlFor="email">Email Address</label>
<input id="email" type="email" />
<button type="submit">Log In</button>
</form>
);
}Connecting a <label> to its input via matching htmlFor / id attributes lets screen readers announce what each field is for, and lets users click the label text itself to focus the input โ a form with only placeholder text and no real labels is a very common, easily fixed accessibility failure.
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<h2 id="modal-title">Confirm Action</h2>
{children}
<button onClick={onClose} aria-label="Close dialog">โ</button>
</div>
);
}ARIA (Accessible Rich Internet Applications) attributes fill gaps HTML alone can't express โ role="dialog" and aria-modal="true" tell assistive technology this <div> behaves like a modal window, and aria-label gives an icon-only button (โ) a meaningful name for screen readers, since there's no visible text to announce otherwise.
import { useRef, useEffect } from "react";
function Modal({ isOpen }) {
const closeButtonRef = useRef(null);
useEffect(() => {
if (isOpen) closeButtonRef.current?.focus();
}, [isOpen]);
return <button ref={closeButtonRef}>Close</button>;
}When a modal opens, moving keyboard focus into it (using useRef, from Chapter 20) is essential for keyboard-only users, who would otherwise have no idea the modal even appeared. Separately, ensure sufficient color contrast between text and its background โ light gray text on a white background may look elegant but is genuinely unreadable for many users with low vision.
Adding outline: none to buttons and links because the default focus ring "looks ugly" removes the only visual indicator keyboard users have for where they currently are on the page. If you want a custom focus style, replace it with an equally visible alternative โ never remove it entirely.
Build a simple accessible form with a properly connected label and input, and a submit button that uses a real
function ContactForm() {
return (
<form>
<label htmlFor="message">Your Message</label>
<textarea id="message" />
<button type="submit">Send Message</button>
</form>
);
}
Q Do I need to add ARIA attributes to every element?
No โ the general rule is 'no ARIA is better than bad ARIA.' Use semantic HTML elements first, since they come with correct accessibility behavior built in, and reach for ARIA attributes only to fill genuine gaps, like the custom modal example above.
Q How can I test if my React app is accessible?
Browser extensions like axe DevTools or Lighthouse (built into Chrome DevTools) automatically scan a page for common accessibility issues. Beyond automated tools, actually navigating your app using only the keyboard (Tab, Enter, Escape) reveals problems tools alone often miss.