React Best Practices

Accessibility (a11y)

ARIA attributes, keyboard navigation, screen reader support, and testing.

Advertisement

Accessibility (a11y)

ARIA attributes, keyboard navigation, screen reader support, and testing.

Overview

Accessibility ensures React apps are usable by everyone, including people with disabilities.

Key Concepts

  • Semantic HTML — Use proper HTML elements for meaning
  • ARIA Attributes — Enhance accessibility when HTML isn't enough
  • Keyboard Navigation — Support tab, enter, escape, arrow keys
  • Screen Readers — Announce dynamic content changes
  • Focus Management — Maintain logical focus order

Code Examples

function AccessibleModal({ isOpen, onClose, title, children }) {
  const closeRef = useRef(null);

  useEffect(() => {
    if (isOpen) closeRef.current?.focus();
  }, [isOpen]);

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

  if (!isOpen) return null;

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

function AccessibleTabs({ tabs, panels }) {
  const [activeIndex, setActiveIndex] = useState(0);

  const handleKeyDown = (e) => {
    if (e.key === 'ArrowRight') setActiveIndex(i => (i + 1) % tabs.length);
    if (e.key === 'ArrowLeft') setActiveIndex(i => (i - 1 + tabs.length) % tabs.length);
  };

  return (
    <div>
      <div role="tablist" onKeyDown={handleKeyDown}>
        {tabs.map((tab, index) => (
          <button
            key={tab.id}
            role="tab"
            aria-selected={index === activeIndex}
            aria-controls={`panel-${tab.id}`}
            tabIndex={index === activeIndex ? 0 : -1}
            onClick={() => setActiveIndex(index)}
          >
            {tab.label}
          </button>
        ))}
      </div>
      {panels.map((panel, index) => (
        <div
          key={panel.id}
          role="tabpanel"
          id={`panel-${panel.id}`}
          hidden={index !== activeIndex}
        >
          {panel.content}
        </div>
      ))}
    </div>
  );
}

Practice

Audit and fix accessibility issues in a React component library.