iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Accessibility

Accessible React is mostly semantic HTML plus a handful of ARIA patterns and disciplined focus management.

React accessibility

EXAMPLE
// 1. Semantic HTML beats div + role
// Wrong
<div onClick={save} className='btn'>Save</div>
// Right
<button type='button' onClick={save}>Save</button>


// 2. Labelled inputs
<label htmlFor='email' className='block text-sm'>Email</label>
<input id='email' type='email' aria-describedby='email-help' />
<p id='email-help' className='text-xs text-gray-500'>We will never share it.</p>


// 3. Focus management on route change
import { useEffect, useRef } from 'react';
import { useLocation } from 'react-router-dom';

function PageTitle({ children }: { children: React.ReactNode }) {
  const h1 = useRef<HTMLHeadingElement>(null);
  const { pathname } = useLocation();
  useEffect(() => { h1.current?.focus(); }, [pathname]);
  return <h1 ref={h1} tabIndex={-1}>{children}</h1>;
}


// 4. Live regions for async status
function Status({ message }: { message: string }) {
  return <div role='status' aria-live='polite'>{message}</div>;
}


// 5. Modal traps + restores focus
import { useEffect, useRef } from 'react';

function Modal({ open, onClose, children }) {
  const ref = useRef<HTMLDivElement>(null);
  const previousFocus = useRef<HTMLElement | null>(null);

  useEffect(() => {
    if (!open) return;
    previousFocus.current = document.activeElement as HTMLElement;
    ref.current?.focus();
    return () => previousFocus.current?.focus();
  }, [open]);

  if (!open) return null;
  return (
    <div role='dialog' aria-modal='true' ref={ref} tabIndex={-1}>
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  );
}


// 6. Custom components - use react-aria or headless UI
// They give you focus order, ARIA wiring, and keyboard support for free.

// 7. Test it
import { render, screen } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

it('has no a11y violations', async () => {
  const { container } = render(<Page />);
  expect(await axe(container)).toHaveNoViolations();
});


// 8. Manual checks - the part axe cannot catch
// - Tab through the page; does it make sense?
// - Use a screen reader (VoiceOver, NVDA) for one page per release
// - Check colour contrast (4.5:1 for text, 3:1 for UI)
// - prefers-reduced-motion respected for transforms

Why it matters

Accessibility is engineering, not a checkbox. Semantic HTML covers 80 percent; ARIA + focus management covers the gap; axe + a screen reader covers regressions. Test a page weekly the way a keyboard-only user would.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<button aria-label="Close" onClick={close}>×</button>
<input id="email" /><label htmlFor="email">Email</label>
Try it Yourself »

Discussion

Loading…