Skip to content
·8 min read

Practical Web Accessibility: What to Actually Fix (and What to Ignore)

WCAG has 1,000 rules. 20 of them cover 80% of real issues. Here's the practical accessibility checklist I use for client projects, focused on impact over checkboxes.

AccessibilityWCAGWeb DevelopmentFrontend

Every time someone publishes an "accessibility checklist" with 80 items, two things happen. Readers give up. And the items that actually matter get buried under WCAG minutiae.

Accessibility has a real 80/20. About twenty fixes cover eighty percent of real-world issues. Here is the list I use for client projects, focused on impact instead of checkboxes.

The high-leverage fixes

These are the things I find on almost every audit and that make an immediate difference for users.

1. Use semantic HTML

The single highest-leverage fix. A <button> gives you:

  • Keyboard activation (Enter and Space)
  • Focus ring by default
  • Screen reader announcement ("button")
  • disabled state support

A <div onClick> gives you none of that. Every interactive element should be a <button>, <a>, <input>, <select>, or <textarea> unless you have a very specific reason.

// Bad
<div onClick={onClick} className="cursor-pointer">Submit</div>
 
// Good
<button onClick={onClick}>Submit</button>

This one rule fixes an enormous percentage of accessibility issues.

2. Label every input

The most common failure I see: form inputs without labels.

// Bad — screen reader announces "edit text, blank"
<input type="email" placeholder="Email" />
 
// Good
<label htmlFor="email">Email</label>
<input id="email" type="email" />
 
// Also good — wrapping label
<label>
  Email
  <input type="email" />
</label>

Placeholders are not labels. They disappear on focus and are read inconsistently by screen readers.

3. Make sure keyboard navigation works

Unplug your mouse. Try to:

  • Reach every interactive element with Tab
  • Activate buttons and links with Enter or Space
  • Navigate form fields with Tab and Shift+Tab
  • Close modals with Escape
  • Skip to main content with a skip link

If you cannot do these, your keyboard support is broken. Fix the underlying semantic HTML before reaching for ARIA.

4. Check color contrast

Use the WCAG AA standard: 4.5:1 for normal text, 3:1 for large text and UI components.

The most common contrast issues:

  • Grey text on white (designers love it, screen readers hate it)
  • White text on light accent colors
  • Placeholder text in form fields

Run your dev build through axe-core or Wave. Both flag contrast issues automatically.

5. Write meaningful link text

// Bad — screen reader user hears "click here, click here, click here"
<a href="/docs">Click here</a>
 
// Good — context travels with the link
<a href="/docs">Read the deployment docs</a>

Screen reader users navigate by skipping between links. "Click here" tells them nothing out of context.

Focus management

Modals

When a modal opens, focus should move to the first interactive element inside it. When it closes, focus returns to the element that triggered it.

Without this, keyboard users get stuck. They Tab through the page behind the modal, unable to see where their focus is.

If you use a modal library (Radix, Headless UI, React Aria), this is handled for you. If you wrote your own, you need to implement it.

Route changes

In a single-page app, a route change does not move focus. A screen reader user clicks "About", the page changes, but their focus stays on the "About" link. They have no idea the content updated.

Fix: move focus to the new page's main heading on route change.

useEffect(() => {
  document.querySelector('h1')?.focus()
}, [pathname])

Make the h1 focusable with tabIndex={-1}.

Skip links

A skip link at the top of the page lets keyboard users jump past the navigation:

<a href="#main" className="sr-only focus:not-sr-only">
  Skip to main content
</a>
 
<main id="main">
  {/* ... */}
</main>

This is a tiny addition that makes a huge difference for keyboard users.

ARIA: use sparingly

The ARIA mantra: no ARIA is better than bad ARIA.

ARIA does not fix broken semantics. It adds metadata to elements. If you used semantic HTML correctly, you usually do not need ARIA.

// Bad — using ARIA to fake a button
<div role="button" tabindex="0" onClick={onClick}>Submit</div>
 
// Good — let HTML do its job
<button onClick={onClick}>Submit</button>

Common ARIA patterns that are legitimate:

  • aria-label on icon-only buttons
  • aria-live for dynamic notifications
  • aria-expanded on disclosure buttons
  • role="dialog" and aria-modal for custom modals (if you cannot use a library)

Everything else: be skeptical.

Testing workflow

Three layers, in order of cost:

Layer 1: Automated (axe-core)

Add to your test suite:

import { axe } from 'vitest-axe'
import { render } from '@testing-library/react'
 
it('has no accessibility violations', async () => {
  const { container } = render(<MyComponent />)
  const results = await axe(container)
  expect(results).toHaveNoViolations()
})

Catches 40-60% of issues. Fast, free, repeatable. Do this in CI on day one.

Layer 2: Manual keyboard testing

Unplug your mouse. Use the site. You will find issues axe cannot catch, like focus traps that do not work, keyboard shortcuts that conflict, and navigation that requires hover states.

Layer 3: Screen reader testing

Use VoiceOver (Cmd+F5 on macOS) or NVDA (free on Windows). You do not need to be a power user. Learn the top five commands and test your site. Most issues are obvious within minutes.

What to ignore

Things that get a lot of attention but rarely matter:

  • Perfect WCAG AAA compliance — AA is the legal and practical target.
  • Custom focus rings on every element — the browser default is fine for most cases. Customize only when the design truly needs it.
  • Switching to 10px minimum font — accessibility best practice says 16px minimum for body text. Anything less is hard to read.
  • ARIA on everything — adds noise, fixes nothing if the underlying HTML is wrong.

Accessibility is not a checklist. It is a habit of using semantic HTML, labeling inputs, and testing with a keyboard. The 80/20 is real — focus on the high-leverage fixes first.

Want an accessibility audit?

I audit React and Next.js apps for accessibility, fix the high-impact issues, and set up automated testing in CI. Let's talk.

Frequently Asked Questions

What is the most common web accessibility mistake?

Using div or span elements with onClick handlers instead of semantic button or a elements. This breaks keyboard navigation, screen reader announcements, and focus handling. Use real HTML elements whenever possible.

Is WCAG compliance required by law?

In many jurisdictions, yes. The Americans with Disabilities Act in the US and the European Accessibility Act apply to websites. Lawsuits around web accessibility are increasing year over year, especially against e-commerce sites.

What does axe-core check?

axe-core is an open-source accessibility testing engine that runs in your browser or test suite. It catches about 40-60% of WCAG violations automatically, including missing labels, color contrast issues, missing ARIA, and broken landmarks.

How do I test a website with a screen reader?

On macOS, enable VoiceOver with Cmd+F5 and use the VoiceOver navigation commands. On Windows, install NVDA (free). Learn the top 5 commands: next element, activate, headings list, forms list, read from here. Test your site, do not try to become a power user.