React can help you build accessible components for users relying on screen readers like JAWS, NVDA, and VoiceOver. Accessibility isn’t just a legal requirement under the ADA and Section 508 – it also improves usability, reduces support costs, and broadens your audience. By following WCAG 2.1 Level AA guidelines, you ensure your app works for everyone.
Here’s what you need to know:
- Semantic HTML: Use native elements (
<button>,<nav>,<header>) whenever possible. They come with built-in roles and behavior that assistive technologies recognize. - WAI-ARIA: Use ARIA roles and attributes (
role,aria-expanded,aria-label) to enhance custom components. Avoid overusing ARIA – it can confuse screen readers if misapplied. - Focus Management: Handle focus shifts programmatically when showing modals, dropdowns, or dynamic content. Use
useRefanduseEffectto manage focus transitions smoothly. - State Updates: Bind ARIA attributes like
aria-expandedoraria-liveto React state to keep users informed of changes. - Testing: Regularly test your components with screen readers and tools like
eslint-plugin-jsx-a11yto catch issues early.
Accessibility isn’t just about compliance – it’s about creating better experiences for everyone. Start small by auditing one component at a time, prioritizing semantic HTML, and testing thoroughly.

5 Essential Steps to Build Accessible React Components with WCAG 2.1 AA Compliance
"How to Build Accessible React Components" by Catherine Johnson at #RemixConf 2023 💿
Building Accessible React Components with WAI-ARIA

WAI-ARIA (Web Accessibility Initiative – Accessible Rich Internet Applications) is a W3C specification that provides roles, states, and properties to improve how assistive technologies interact with web applications. One key principle of WAI-ARIA is: "No ARIA is better than bad ARIA." This means that improperly used ARIA roles or states can mislead screen readers, such as labeling a clickable <div> as a button without proper keyboard functionality. To avoid these issues, developers should prioritize semantic HTML and only use ARIA when native elements can’t achieve the desired behavior or structure.
The U.S. CDC reports that 1 in 4 adults in the United States has a disability, many of whom rely on assistive technologies like screen readers. This highlights the ethical and legal importance of designing accessible interfaces. ARIA becomes especially useful when building custom components from elements like <div> or <span> or creating complex widgets such as menus, tabs, and dialogs. It bridges the gap between native HTML semantics and the requirements of assistive technologies.
Using ARIA Roles in React
ARIA roles define what a component is for assistive technologies. React supports ARIA attributes directly through JSX, allowing you to use properties like role, aria-expanded, and aria-label seamlessly. For example, if you’re building a custom button using <div> or <span>, you can add role="button", tabIndex={0}, and handle both onClick and keyboard events (e.g., Enter and Space) for proper functionality.
Here’s an example of a custom button component:
function IconButton({ onActivate }) { const handleKeyDown = (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onActivate(); } }; return ( <div role="button" tabIndex={0} aria-label="Open settings" onClick={onActivate} onKeyDown={handleKeyDown} > ⚙️ </div> ); }
For more complex widgets like menus, assign role="menu" to the container and role="menuitem" (or menuitemcheckbox/menuitemradio) to the items. Implement arrow-key navigation in React since ARIA does not include built-in behavior for these roles. Similarly, for dialogs, use role="dialog" on the modal wrapper, pair it with aria-modal="true", and manage focus within the dialog until it is closed. Always ensure that the ARIA role reflects the component’s actual behavior.
Communicating Interactive States with ARIA Properties
ARIA roles work best when paired with properties that communicate state changes. Binding ARIA attributes like aria-expanded or aria-pressed to component state ensures that updates are reflected in the UI immediately. For example, a toggle button should use aria-pressed={isOn} to indicate its state, while elements like accordions or dropdowns should use aria-expanded={isOpen} and aria-controls to link to the relevant content.
Here’s an example of an accessible FAQ component:
function FAQItem({ question, children }) { const [open, setOpen] = React.useState(false); const panelId = `faq-panel-${question.replace(/\s+/g, '-').toLowerCase()}`; return ( <div> <button aria-expanded={open} aria-controls={panelId} onClick={() => setOpen(!open)} > {question} </button> {open && ( <div id={panelId} role="region"> {children} </div> )} </div> ); }
When state changes, React automatically updates attributes like aria-expanded, enabling screen readers to announce whether a section is "expanded" or "collapsed." In selection-based widgets like tabs or listboxes, use aria-selected to indicate the active option. For tabs, each element should have role="tab" with the appropriate aria-selected value. The active tab should also have tabIndex={0}, while inactive tabs use tabIndex={-1}.
For custom widgets that don’t support native disabled attributes, use aria-disabled="true". However, keep in mind that aria-disabled won’t block interactions, so you must prevent clicks and key events in your code.
For dynamic updates, use aria-live regions to notify screen readers of changes. For example, aria-live="polite" informs users of non-urgent updates like form errors, while aria-live="assertive" is reserved for critical messages. Be cautious not to overwhelm users with frequent or unnecessary announcements.
Finally, always test your ARIA implementations with screen readers like NVDA or VoiceOver. Tools like eslint-plugin-jsx-a11y can also help identify accessibility issues in your code. Regular testing ensures that your components function as intended for all users.
Using Semantic HTML with React
Using semantic HTML is a smart way to make your React applications more accessible. Elements like <button>, <header>, <nav>, and <main> naturally convey structure and meaning, which helps screen readers interpret roles, states, and relationships. Since React’s JSX compiles to standard HTML, incorporating these elements directly into your components ensures accessibility without requiring additional ARIA attributes. This builds on the foundational accessibility principles discussed earlier.
Relying too much on <div> and <span> for interactive elements can create problems for assistive technologies. These generic tags lack inherent roles, which means developers often have to manually add ARIA attributes to make them usable. This can lead to a "div soup", where screen reader users are forced to navigate linearly through a page without clear headings or landmarks. This slows down their experience and makes navigation more cumbersome.
Using Native HTML Elements for Accessibility
React developers should always lean toward native interactive elements because they come with built-in keyboard navigation, activation behaviors, and screen reader support. For example, a button implemented like this:
<button type="button" onClick={handleSave}> Save changes </button>
is automatically focusable, keyboard accessible, and correctly announced by screen readers. In contrast, using a <div> for the same purpose:
<div onClick={handleSave}> Save changes </div>
requires extra work, including adding attributes like role="button", tabIndex="0", and custom keyboard handlers. Even with these additions, the experience often falls short of what native elements provide.
For navigation, always use an <a> element with an href attribute. This ensures screen readers can recognize links and provide navigation-specific shortcuts. When using tools like React Router, the <Link> component should render a proper <a> tag underneath. Similarly, it’s best to stick with standard form elements like <form>, <label>, <fieldset>, and <input>, as these come with built-in accessibility features. Avoid creating custom controls unless absolutely necessary.
When organizing content, opt for semantic tags over generic containers. This helps screen readers announce heading levels and structural regions accurately, making navigation smoother.
Structuring Pages with Landmarks
Landmarks are essential for creating a logical page structure. They act as shortcuts for screen readers, allowing users to quickly jump between key areas like navigation, main content, and footers. Semantic elements naturally align with these roles: <nav> marks navigation areas, <main> identifies the primary content (used only once per page), and <header> and <footer> define banners and content sections.
In React, you can build layouts with these landmarks to enhance accessibility:
function Layout({ children }) { return ( <> <header> <h1>Site Title</h1> </header> <nav aria-label="Primary"> {/* Main site navigation links */} </nav> <main>{children}</main> <footer>© 2025 Example, Inc.</footer> </> ); }
For pages with multiple navigation areas, use descriptive labels to differentiate them. For example, <nav aria-label="Primary"> can mark the main navigation, while <nav aria-label="Account"> can handle user-related links. Similarly, you can label sidebars or secondary sections with attributes like <aside aria-label="Filters"> or <section aria-labelledby="support-heading">. These labels help screen readers identify each area clearly.
You generally don’t need to add ARIA landmark roles (like role="main" or role="navigation") when using semantic elements – browsers already expose these roles to assistive technologies. Reserve ARIA roles for cases where semantic elements aren’t an option or when supporting very old browsers. The key takeaway is to prioritize native semantics and use ARIA sparingly to fill gaps. This approach complements the ARIA techniques we’ve previously discussed.
sbb-itb-f6354c6
Managing Focus and State in React
Ensuring accessible dynamic interfaces in React requires careful attention to focus and state management. Features like modals, dropdowns, and toasts can confuse screen reader users if focus isn’t properly controlled. When content appears or disappears, users relying on keyboards or assistive technologies need clear navigation paths to avoid losing their place. React provides tools to programmatically manage focus and announce state changes, making these dynamic updates more accessible.
Focus Management in Dynamic Interfaces
When opening a modal, focus should immediately shift to a relevant element inside it – usually a close button or a heading with tabIndex="-1". Before moving focus, store the currently focused element using document.activeElement in a ref. Once the modal closes, you can call .focus() on that stored element to return users to their previous position, preserving a logical navigation flow.
In React, useRef is particularly useful for holding references to DOM nodes. By combining it with a useEffect hook, you can programmatically call .focus() when a component mounts or updates. For example, when a dropdown menu opens, focus should move to the first item. When it closes, focus should return to the toggle button. This approach also applies to drawers, popovers, and other dynamic UI components.
For dropdowns and popovers, attaching onFocus and onBlur handlers to the parent element can help manage focus transitions smoothly. A handy technique is to delay closing the popover on onBlur using setTimeout and cancel the timeout in onFocus if focus shifts to another element inside the popover. This prevents accidental closures when users tab between items. React’s documentation includes an example that demonstrates these patterns effectively.
In single-page applications (SPAs), route changes don’t trigger full page reloads, which can leave screen readers unaware of new content. To address this, create a focusable main container – <main tabIndex="-1" ref={contentRef}> – and call contentRef.current.focus() whenever the route changes. This action moves the virtual cursor to the top of the new content, mimicking the behavior of a traditional page load and ensuring screen readers announce the updated page.
These focus management strategies lay the groundwork for effectively using ARIA live regions to communicate real-time state changes.
Using ARIA States for Dynamic Components
ARIA live regions allow you to announce updates to screen readers without disrupting keyboard focus. For status updates, include a visually hidden <div aria-live="polite" aria-atomic="true">. Use aria-live="assertive" sparingly for urgent messages or errors. When the application state changes, update the text content of the live region via React state, prompting screen readers to read the update.
To reflect state changes in components, bind ARIA attributes to the component’s state. For example, a disclosure button controlling a collapsible panel should use aria-expanded={isOpen} and aria-controls="panel-id". When isOpen changes, React updates the attributes, and screen readers announce whether the panel is "expanded" or "collapsed." Similarly, a toggle button can use aria-pressed={isOn} to indicate its on/off state, while list items in a tablist or selectable list can use aria-selected={isSelected} to signal which item is active.
For form validation, keep the keyboard focus on the first invalid field and use an aria-live="assertive" or "polite" region to summarize errors. After form submission, calculate the errors, focus the first invalid input using a ref, and update the live region with a summary like "3 errors on this form. Name is required. Email must be a valid address." Each input should link to its error message via aria-describedby="field-error-id" and include aria-invalid="true" to indicate a problem.
Prototyping Accessible React Components in UXPin

Prototyping accessible components in UXPin brings focus management and ARIA states into the design process from the start. With UXPin’s code-backed prototyping, you can create interactive React prototypes using both built-in and custom component libraries that include WAI-ARIA attributes. This setup lets you test ARIA roles and states directly in your prototypes, ensuring that the semantic structure and focus management behave as they would in a live application. By aligning with the ARIA techniques and focus strategies previously discussed, this method makes accessibility testing an integral part of the design workflow. According to case studies, teams using UXPin’s accessible libraries achieve WCAG 2.1 AA compliance three times faster, with screen reader errors in prototypes dropping by 70%.
Using Built-in React Libraries in UXPin
UXPin offers built-in React libraries like MUI (Material-UI), Tailwind UI, and Ant Design, which are designed with native support for ARIA roles, semantic HTML landmarks, and keyboard navigation. These pre-built components are tested with screen readers like NVDA and VoiceOver, minimizing the need for additional accessibility coding. For example:
- MUI: Components like Button and TextField automatically apply ARIA attributes and focus states, enabling prototypes to announce statuses such as "required field" or "invalid entry" to screen readers.
- Ant Design: Table and List components support ARIA roles, announce dynamic states, and provide robust keyboard navigation.
- Tailwind UI: The Modal component comes pre-configured with attributes like
role="dialog",aria-modal="true", andaria-labelledby. It also usesuseReffor focus management, allowing screen readers to announce states like "Dialog, submit or cancel."
These libraries simplify accessibility features, while custom components allow for more tailored experiences.
Creating Custom Accessible React Components
UXPin also enables you to import custom React components by syncing your Git repositories. You can add ARIA attributes like aria-expanded or aria-live to these components to clearly communicate interactive states. For instance, a custom toggle component using aria-pressed={isToggled} ensures that screen readers announce state changes in real time, continuing the accessibility principles discussed earlier.
Additionally, UXPin’s preview mode includes tools like screen reader simulation for NVDA and VoiceOver, keyboard-only navigation testing, and an ARIA inspector to verify that roles and states align with WAI-ARIA standards.
Brian Demchak, Sr. UX Designer at AAA Digital & Creative Services, highlights the value of UXPin Merge:
"As a full stack design team, UXPin Merge is our primary tool when designing user experiences. We have fully integrated our custom-built React Design System and can design with our coded components. It has increased our productivity, quality, and consistency, streamlining our testing of layouts and the developer handoff process."
Conclusion
This guide has walked through the key steps to make your React components more accessible and user-friendly. Now it’s time to put these strategies into practice.
By focusing on accessibility, you’re not just meeting compliance standards – you’re creating better experiences for everyone. Using tools like semantic HTML, WAI-ARIA, and proper focus management ensures your React apps work seamlessly with assistive technologies like NVDA and VoiceOver, preventing the need for costly fixes down the line.
Start small: audit one component per sprint. Add semantic landmarks, refine keyboard navigation, and restore focus properly in modals. Avoid relying too heavily on custom elements without ARIA support, and don’t skip keyboard testing – it’s essential for ensuring usability.
Tools like UXPin make this process smoother by allowing you to prototype and test accessibility features early on. Validate ARIA roles, focus order, and landmarks before development even begins, turning accessibility into a core part of your design workflow.
FAQs
How do I make my React components accessible for screen readers?
To ensure your React components are accessible to screen readers, start by using semantic HTML elements – for example, opt for <button> or <header> instead of generic tags like <div> or <span>. These elements inherently provide meaning and structure, making it easier for assistive technologies to interpret your content.
When necessary, you can enhance accessibility by adding ARIA attributes such as aria-label or aria-hidden, or assigning specific roles. Use these sparingly and only when semantic HTML alone doesn’t convey the required context or functionality.
It’s also essential to test your components with screen readers to confirm they offer clear and intuitive navigation. Pay close attention to focus management, ensuring users can seamlessly interact with your interface using a keyboard or other assistive tools. By adhering to these practices, you can create interfaces that are more inclusive and user-friendly for everyone.
What are the key best practices for using WAI-ARIA in React apps?
To make the most of WAI-ARIA in React applications, it’s important to assign the right roles to elements, use ARIA attributes to clearly indicate states (like expanded or selected), and ensure ARIA labels are updated dynamically to reflect any changes in the user interface. Managing focus effectively is also key to providing smooth navigation for users relying on screen readers.
It’s essential to test your app with screen readers regularly to confirm accessibility. Following the official WAI-ARIA guidelines will help ensure your application remains compatible with assistive technologies, creating a more inclusive experience for all users.
How can I handle focus and state updates in dynamic React components for better accessibility?
When working with dynamic React components, it’s crucial to prioritize accessibility. One effective approach is to manage focus by programmatically directing it to the relevant elements after updates. Additionally, implementing ARIA live regions ensures that screen readers can announce content changes, keeping users informed. Don’t forget to update ARIA attributes to accurately reflect any state changes. These practices ensure that screen readers provide users with a seamless and inclusive experience, especially when real-time updates occur in the interface.