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.
Related Insights

Accessibility by Design: Building Inclusive Digital Products from Day One
Build accessible digital products from day one with inclusive design principles, WCAG compliance strategies, and practical implementation patterns.

Accessibility Testing Automation: axe-core, Lighthouse, and CI Integration
Learn automated accessibility testing with axe-core, Lighthouse CI, and integration into CI/CD pipelines for catching issues before they reach production.

Code Splitting and Lazy Loading in React: Performance Optimization Guide
A comprehensive guide to code splitting and lazy loading in React applications including React.lazy, Suspense, route-based splitting, and component-level chunking.