SiteDNA
Back to Blog

Making React Components Accessible: A Complete Guide

Build inclusive React applications with proper ARIA attributes, keyboard navigation, and focus management.

Making React Components Accessible: A Complete Guide

React's component-based architecture makes it powerful for building complex UIs, but it also makes it easy to accidentally create inaccessible interfaces. This guide covers essential patterns for building accessible React applications.

Semantic HTML First

Before reaching for ARIA, use semantic HTML elements. They have built-in accessibility features.

// Bad: div with click handler
<div onClick={handleClick}>Submit</div>

// Good: native button
<button onClick={handleClick}>Submit</button>

Native elements provide:

  • Keyboard accessibility (Enter/Space to activate buttons)
  • Proper role announcements to screen readers
  • Focus management

Managing Focus

Single-page applications often fail to manage focus when content changes. When navigating to a new "page," focus should move to the new content.

import { useRef, useEffect } from 'react';

function PageContent({ title, children }) {
  const headingRef = useRef(null);

  useEffect(() => {
    // Focus the heading when page loads
    headingRef.current?.focus();
  }, []);

  return (
    <main>
      <h1 ref={headingRef} tabIndex={-1}>{title}</h1>
      {children}
    </main>
  );
}

Accessible Forms

Every form input needs an associated label.

function TextField({ label, id, ...props }) {
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} {...props} />
    </div>
  );
}

Error Messages

function TextField({ label, id, error, ...props }) {
  const errorId = `${id}-error`;

  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input
        id={id}
        aria-invalid={error ? 'true' : undefined}
        aria-describedby={error ? errorId : undefined}
        {...props}
      />
      {error && (
        <span id={errorId} role="alert">
          {error}
        </span>
      )}
    </div>
  );
}

Live Regions for Dynamic Content

When content updates dynamically, screen readers need to be notified.

function SearchResults({ results, isLoading }) {
  return (
    <div>
      <div aria-live="polite" aria-busy={isLoading}>
        {isLoading && <span>Loading results...</span>}
      </div>

      <div aria-live="polite">
        {!isLoading && (
          <span>{results.length} results found</span>
        )}
      </div>

      <ul>
        {results.map(result => (
          <li key={result.id}>{result.title}</li>
        ))}
      </ul>
    </div>
  );
}

Testing React Accessibility

Use these tools to catch issues during development:

  • eslint-plugin-jsx-a11y: Catches common issues at build time
  • React DevTools: Inspect component props and state
  • axe DevTools: Browser extension for runtime testing
  • SiteDNA: Full-site scanning to catch issues across all pages

Key Takeaways

  • Use semantic HTML before ARIA
  • Manage focus when content changes
  • Associate labels with all form inputs
  • Trap focus in modals and restore it on close
  • Use live regions for dynamic updates
  • Test with keyboard navigation and screen readers