Abhay Bhardwaj — Design Engineer

Jan 15, 20263 min read

Building Accessible React Components

A deep dive into creating inclusive user interfaces that work for everyone.

Article

Building Accessible React Components

Creating accessible web applications isn't just about following guidelines—it's about ensuring everyone can use what we build. In this article, we'll explore practical techniques for building inclusive React components.

Why Accessibility Matters

Web accessibility ensures that people with disabilities can perceive, understand, navigate, and interact with websites. This includes people who are:

  • Visually impaired — using screen readers or magnification
  • Hearing impaired — relying on captions and transcripts
  • Motor impaired — using keyboards or alternative input devices
  • Cognitively impaired — benefiting from clear navigation and content

Semantic HTML First

Before reaching for ARIA attributes, use semantic HTML elements:

// ❌ Bad - div with click handler
<div onClick={handleClick} className="button">
  Click me
</div>
 
// ✅ Good - semantic button element
<button onClick={handleClick} className="button">
  Click me
</button>

Managing Focus

Focus management is crucial for keyboard navigation. Here's a custom hook for managing focus:

import { useRef, useEffect } from "react";
 
export function useFocusTrap(isActive: boolean) {
  const containerRef = useRef<HTMLDivElement>(null);
 
  useEffect(() => {
    if (!isActive) return;
 
    const container = containerRef.current;
    if (!container) return;
 
    const focusableElements = container.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
    );
 
    const firstElement = focusableElements[0] as HTMLElement;
    const lastElement = focusableElements[
      focusableElements.length - 1
    ] as HTMLElement;
 
    firstElement?.focus();
 
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key !== "Tab") return;
 
      if (e.shiftKey && document.activeElement === firstElement) {
        e.preventDefault();
        lastElement?.focus();
      } else if (!e.shiftKey && document.activeElement === lastElement) {
        e.preventDefault();
        firstElement?.focus();
      }
    };
 
    container.addEventListener("keydown", handleKeyDown);
    return () => container.removeEventListener("keydown", handleKeyDown);
  }, [isActive]);
 
  return containerRef;
}

Accessible Form Components

Forms are where accessibility often falls short. Here's a properly accessible input component:

interface InputProps {
  id: string;
  label: string;
  error?: string;
  required?: boolean;
}
 
export function AccessibleInput({
  id,
  label,
  error,
  required,
  ...props
}: InputProps & React.InputHTMLAttributes<HTMLInputElement>) {
  const errorId = `${id}-error`;
 
  return (
    <div className="field">
      <label htmlFor={id}>
        {label}
        {required && <span aria-hidden="true"> *</span>}
      </label>
      <input
        id={id}
        aria-required={required}
        aria-invalid={!!error}
        aria-describedby={error ? errorId : undefined}
        {...props}
      />
      {error && (
        <p id={errorId} role="alert" className="error">
          {error}
        </p>
      )}
    </div>
  );
}

Testing Accessibility

Use these tools to test your components:

ToolPurpose
axe DevToolsAutomated accessibility testing
NVDA/VoiceOverScreen reader testing
Keyboard onlyManual keyboard navigation
Color contrastCheck WCAG contrast ratios

Conclusion

Building accessible components requires intentional effort, but the payoff is significant. Not only do you create better experiences for users with disabilities, but you also improve usability for everyone.

"The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect." — Tim Berners-Lee