Skip to content

Accessible Components React

DodaTech 4 min read

title: "Building Accessible Components in React" weight: 19 description: "Learn patterns for building accessible React components: accordion, tabs, modal, tooltip, autocomplete, and slider with proper HTML, ARIA, keyboard support, focus management, and screen reader testing." date: 2026-06-28 lastmod: 2026-06-28 tags: [accessibility, react]


Building accessible React components means combining semantic HTML, conditional ARIA attributes, keyboard event handling, focus management, and live region announcements into reusable component patterns that enforce accessibility at every instance.

## What You'll Learn

You will build accessible versions of common UI components: accordion, tabs, modal, tooltip, and slider -- following ARIA authoring practices and React best practices.

## Why It Matters

Reusable accessible components are the foundation of an accessible React application. A well-built component library means developers get accessibility for free every time they use a component.

## Real-World Use

A team creates an accessible Modal component with focus trapping, Escape handling, and screen reader announcements. Every modal in the app uses this component. A new developer adds a confirmation modal in 5 minutes, and it is automatically accessible.

## Accessible Component Patterns

```mermaid
flowchart TD
  A[Component] --> B[Semantic HTML]
  A --> C[ARIA Attributes]
  A --> D[Keyboard Support]
  A --> E[Focus Management]
  A --> F[Screen Reader Support]

Component Examples

Each component follows ARIA authoring practices and includes all accessibility requirements.

function Accordion({ items }) {
  const [openIndex, setOpenIndex] = useState(null);

  return (
    <div>
      {items.map((item, index) => {
        const isOpen = openIndex === index;
        const panelId = `panel-${index}`;
        const buttonId = `button-${index}`;

        return (
          <div key={index}>
            <button
              id={buttonId}
              aria-expanded={isOpen}
              aria-controls={panelId}
              onClick={() => setOpenIndex(isOpen ? null : index)}
            >
              {item.title}
            </button>
            <div
              id={panelId}
              role="region"
              aria-labelledby={buttonId}
              hidden={!isOpen}
            >
              {item.content}
            </div>
          </div>
        );
      })}
    </div>
  );
}
function Tooltip({ text, children }) {
  const [visible, setVisible] = useState(false);
  const tooltipId = useId();

  return (
    <span
      onMouseEnter={() => setVisible(true)}
      onMouseLeave={() => setVisible(false)}
      onFocus={() => setVisible(true)}
      onBlur={() => setVisible(false)}
    >
      <span aria-describedby={tooltipId}>
        {children}
      </span>
      {visible && (
        <div
          id={tooltipId}
          role="tooltip"
          className="tooltip"
        >
          {text}
        </div>
      )}
    </span>
  );
}
function AccessibleModal({ isOpen, onClose, title, children }) {
  const closeRef = useRef(null);
  const previousFocus = useRef(null);
  const titleId = useId();

  useEffect(() => {
    if (isOpen) {
      previousFocus.current = document.activeElement;
      closeRef.current.focus();
    } else if (previousFocus.current) {
      previousFocus.current.focus();
    }
  }, [isOpen]);

  useEffect(() => {
    const handleKey = (e) => {
      if (e.key === 'Escape' && isOpen) onClose();
    };
    document.addEventListener('keydown', handleKey);
    return () => document.removeEventListener('keydown', handleKey);
  }, [isOpen, onClose]);

  if (!isOpen) return null;

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby={titleId}
      className="modal-overlay"
    >
      <div className="modal-content">
        <h2 id={titleId}>{title}</h2>
        {children}
        <button ref={closeRef} onClick={onClose} aria-label="Close modal">
          Close
        </button>
      </div>
    </div>
  );
}

Common Mistakes

  • Building components that work only with mouse
  • Not following ARIA authoring practices for specific patterns
  • Creating components that are not composable
  • Hard-coding ARIA values that should be dynamic
  • Not testing components with screen readers
  • Building monolithic components that mix multiple patterns
  • Ignoring platform-specific accessibility differences

Practice and Challenge

Practice 1: Build an accessible accordion component. Practice 2: Build accessible tabs with keyboard navigation. Practice 3: Build an accessible modal with focus trap. Practice 4: Add a tooltip that works on hover and focus. Practice 5: Build an accessible slider component.

Challenge: Create a React component library with 5 fully accessible components: Accordion, Tabs, Modal, Tooltip, and Slider. Each component must include proper semantic HTML, ARIA attributes, keyboard navigation, focus management, and screen reader support. Test each component with axe-core and a screen reader.

FAQ

What is the most important accessibility pattern for React?

Focus management is the most frequently needed pattern -- managing focus on modals, navigation, and dynamic content.

How do I make a component keyboard accessible?

Add onKeyDown handlers for relevant keys (Enter, Space, Arrow, Escape) and ensure the component is focusable with tabIndex.

Should I use a library like React Aria?

React Aria provides accessible primitives. You can also build your own following ARIA authoring practices.

How do I test component accessibility?

Use jest-axe for unit tests, storybook with axe for visual testing, and screen reader testing for final verification.

What is the best approach for accessible components?

Encapsulate all accessibility logic in the component so consumers get accessibility automatically.

How do I handle complex components like dialogs?

Follow the dialog ARIA pattern: role='dialog', aria-modal, focus trap, Escape to close, focus return on close.

Mini Project

Build a complete accessible component library in React with: Accordion, Tabs (tablist, tab, tabpanel), Modal (dialog with focus trap), Tooltip (hover and focus), Slider (arrow keys, Home/End), Menu Button (with dropdown), and Disclosure (expand/collapse). Each component must pass axe-core tests and work with NVDA and VoiceOver.

What's Next

React Aria Components covers using Adobe's React Aria library for accessible components.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro