Building Accessible React Applications: WCAG 2.2 Compliance Guide | SoniNow Blog

Limited TimeLearn More

accessibilitywcagreacta11yinclusive design

Building Accessible React Applications: WCAG 2.2 Compliance Guide

Published

2026-06-23

Read Time

4 mins

Building Accessible React Applications: WCAG 2.2 Compliance Guide

WCAG 2.2 introduced nine new success criteria, including focus not obscured (2.4.11), dragging movements (2.5.7), and accessible authentication (3.3.8). For React developers, compliance means more than adding aria- attributes — it demands rethinking component architecture from the ground up. Here is how to build React applications that meet WCAG 2.2 standards without sacrificing developer experience.

Semantic HTML: The Foundation of Accessibility

Before touching ARIA, get your HTML structure right. React components should render native semantic elements wherever possible. A button should be a <button>, not a <div onClick> with role="button". Navigation should use <nav>, and headings should form a logical hierarchy.

// ❌ Bad: div-based button
function BadButton({ onClick, children }) {
  return (
    <div 
      role="button" 
      tabIndex={0} 
      onClick={onClick} 
      onKeyDown={(e) => e.key === 'Enter' && onClick()}
    >
      {children}
    </div>
  )
}

// ✅ Good: native button
function GoodButton({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>
}

Native elements come with built-in keyboard handling, focus management, and screen reader announcements. Every abstracted component library should fall back to semantic HTML at its core.

ARIA Live Regions and Dynamic Content

React's dynamic updates require careful ARIA live region management. When content changes without a page navigation — toast notifications, loading states, search results — use aria-live regions to announce changes.

function ToastManager() {
  const [toasts, setToasts] = useState<Toast[]>([])

  return (
    <div aria-live="polite" aria-atomic="false" className="toast-container">
      {toasts.map((toast) => (
        <div key={toast.id} role="status" className="toast">
          {toast.message}
        </div>
      ))}
    </div>
  )
}

Use aria-live="polite" for non-urgent announcements and aria-live="assertive" for critical updates like errors. The aria-atomic attribute controls whether the entire region or just the changed part is announced.

Keyboard Navigation and Focus Management

WCAG 2.2 success criterion 2.4.11 (Focus Not Obscured) requires that focus indicators are fully visible and not hidden behind other elements. Implement custom focus management for modals, dropdowns, and side panels.

import { useRef, useEffect } from 'react'

function Modal({ isOpen, onClose, children }: ModalProps) {
  const modalRef = useRef<HTMLDivElement>(null)
  const previousFocus = useRef<HTMLElement | null>(null)

  useEffect(() => {
    if (isOpen) {
      previousFocus.current = document.activeElement as HTMLElement
      modalRef.current?.focus()
    } else {
      previousFocus.current?.focus()
    }
  }, [isOpen])

  // Trap focus within modal
  useEffect(() => {
    if (!isOpen) return
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') onClose()
      if (e.key === 'Tab') trapFocus(e, modalRef.current)
    }
    document.addEventListener('keydown', handleKeyDown)
    return () => document.removeEventListener('keydown', handleKeyDown)
  }, [isOpen, onClose])

  return (
    <div
      ref={modalRef}
      role="dialog"
      aria-modal="true"
      aria-label="Dialog"
      tabIndex={-1}
    >
      {children}
    </div>
  )
}

Automated Testing with axe-core and Testing Library

Integrate accessibility testing into your pipeline. Use jest-axe with React Testing Library to catch violations in CI.

import { render } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'

expect.extend(toHaveNoViolations)

it('should have no accessibility violations', async () => {
  const { container } = render(<Application />)
  const results = await axe(container)
  expect(results).toHaveNoViolations()
})

Pair automated tests with manual keyboard-only testing. No automated tool catches all WCAG 2.2 issues — focus trapping, color contrast, and screen reader announcements require human verification.

Accessible Authentication (3.3.8)

WCAG 2.2 mandates that authentication mechanisms do not rely on cognitive function tests. Offer alternatives to CAPTCHAs: checkbox confirmation, device-based verification, or time-based one-time passwords.

function LoginForm() {
  return (
    <form>
      <label htmlFor="email">Email</label>
      <input id="email" type="email" autoComplete="email" />

      <label htmlFor="password">Password</label>
      <input id="password" type="password" autoComplete="current-password" />

      {/* Accessible alternative to CAPTCHA */}
      <input type="checkbox" id="verify-human" required />
      <label htmlFor="verify-human">I am not a robot</label>

      <button type="submit">Sign In</button>
    </form>
  )
}

Building Accessibility Into Your Workflow

Accessibility is not a final polish — it is a design constraint. Define color tokens that meet 4.5:1 contrast ratios, establish focus indicators in your design system, and enforce lint rules with eslint-plugin-jsx-a11y. When every component considers keyboard users and screen readers from inception, you meet WCAG 2.2 without retrofitting.

At SoniNow, accessibility is baked into every project we deliver. Our web development services include WCAG 2.2 compliance audits, remediation, and accessible component libraries.

Build for everyone. Partner with SoniNow to make your React application WCAG 2.2 compliant from the ground up.