Frontend

A WCAG Checklist Developers Can Finish Before Standup

Accessibility work stuck on our backlog until I turned WCAG into a short, shippable checklist for every PR. Here is what I actually verify before calling a UI done.

What you will learn

  • Bake keyboard, names, forms, and contrast into every UI PR—not a late sprint.
  • Prefer native HTML controls; add ARIA only when you must extend behavior.
  • Icon-only controls need aria-label; placeholders are not labels.
  • Tie error messages to inputs with aria-describedby and announce them.
  • Check contrast in the rendered page and honor prefers-reduced-motion.
  • Use a short PR checklist plus an automated scanner for regressions.

Stop treating a11y as a phase

We once scheduled a two-week “accessibility sprint” after launch. Half the issues were buttons that were divs, forms without labels, and focus traps in modals. Fixing them late meant redesigning components customers already learned.

WCAG 2.2 AA is the bar most clients ask for. You do not need to memorize every success criterion. You need a repeatable check that catches the failures users hit every day: cannot tab, cannot read, cannot submit, cannot dismiss.

Legal risk is real in some markets, but I pitch a11y as product quality: more users can complete checkout. That framing gets budget.

Keyboard: if you cannot tab it, it is broken

I unplug the mouse and walk the happy path. Tab order should follow visual order. Interactive elements need a visible focus ring—do not remove outline without a replacement.

Custom controls must be reachable and operable with Enter/Space. Escape should close dialogs. Focus must move into a modal on open and return to the trigger on close. I have shipped a date picker that trapped focus forever; support tickets wrote themselves.

  • All actions reachable via keyboard
  • Visible :focus-visible styles on controls
  • Modals: focus trap, Escape, restore focus
  • No keyboard traps in carousels or menus
  • Skip link to main content on content-heavy pages

Names, roles, and the accessibility tree

Screen readers announce accessible names. An icon-only button with no aria-label is silence. Prefer visible text. When the design is icon-only, add aria-label that describes the action, not the icon shape.

Use native elements first: button, a, input, select. Role soup on divs is harder to get right. If you must build a custom widget, match the ARIA authoring practices for that pattern and test with VoiceOver or NVDA.

<!-- Bad -->
<div class="icon-btn" onclick="deleteItem()">🗑</div>

<!-- Better -->
<button type="button" class="icon-btn" aria-label="Delete item" onclick="deleteItem()">
  <span aria-hidden="true">🗑</span>
</button>

<!-- Links go places; buttons do things -->
<a href="/settings">Settings</a>
<button type="button" onclick="save()">Save</button>

Forms that fail for the right reasons

Every input needs a label associated with for/id or wrapping label. Placeholder is not a label. Error text must be tied to the field with aria-describedby and announced when validation runs.

Required fields need both visual indication and aria-required or the required attribute. Do not rely on color alone for errors.

Autocomplete attributes on auth and checkout fields help password managers and reduce errors. They are part of usable accessibility.

<label for="email">Email</label>
<input
  id="email"
  name="email"
  type="email"
  autocomplete="email"
  aria-invalid="true"
  aria-describedby="email-error"
  required
/>
<p id="email-error" class="error" role="alert">Enter a valid email address.</p>

Color, contrast, and motion

Body text should meet contrast ratios against its background. I use the browser DevTools contrast checker on real components, not just the design file, because CSS overlays change effective contrast.

Never convey state with color alone—add text or icons. Respect prefers-reduced-motion: disable non-essential animation when the user asks for it. Autoplaying video with sound is an automatic fail in my review.

  • Text contrast roughly 4.5:1 for normal text (AA)
  • UI component contrast for icons and borders where required
  • Status not by color alone
  • prefers-reduced-motion media query honored
  • Images have meaningful alt; decorative images alt=""

Images, media, and page structure

Alt text describes purpose. Decorative images get empty alt so readers skip them. Charts need a text summary or data table nearby—alt alone rarely carries a complex chart.

One h1 per page, heading levels in order. Landmarks (header, nav, main, footer) help skip navigation. I catch heading skips in PR review more often than exotic ARIA bugs.

My PR checklist (copy this)

I paste a short version into the PR template. If any box fails, the UI is not done.

  • Keyboard-only pass of the changed flow
  • Focus visible; modals manage focus
  • Buttons/links have accessible names
  • Form labels and error announcements present
  • Contrast checked on new text and controls
  • Automated axe or eslint-plugin-jsx-a11y clean on touched files

Automated tools and their blind spots

axe-core, Lighthouse, and eslint-plugin-jsx-a11y catch missing alt, contrast failures, and duplicate labels. They do not catch “the checkout flow makes no sense when tabbing” or “the toast disappears before a screen reader finishes.” Automation is a gate, not a certificate.

I run axe in CI on storybook or critical routes. Failures block merge. I still manually keyboard-test flows that change focus management. That split keeps noise low and coverage real.

When design hands over a component, I ask for focus states and error text in the spec. Retrofitting focus rings after visual QA freezes the design is how a11y work gets cut.

Dynamic content and live regions

SPAs update the page without full reloads. Screen reader users need announcements for important changes. aria-live regions for form success, cart updates, and filter result counts prevent silent UI changes.

Do not mark the entire page as live. That creates announcement storms. Prefer polite live regions for status and assertive only for critical errors. Clear the message node carefully so repeated identical messages still fire when needed.

<div aria-live="polite" aria-atomic="true" class="sr-only" id="status"></div>
<script>
function announce(msg) {
  const el = document.getElementById('status');
  el.textContent = '';
  requestAnimationFrame(() => { el.textContent = msg; });
}
announce('3 products matched your filters');
</script>

Mobile touch targets and zoom

WCAG 2.2 pays more attention to target size. Tiny icon hit areas frustrate motor impairments and everyone on a bumpy train. I aim for about 24×24 CSS pixels minimum, larger when the UI allows.

Do not disable pinch zoom with maximum-scale=1. Users with low vision rely on zoom. If your layout breaks when zoomed, fix the layout.

Touch and keyboard both matter on hybrid devices. A control that works with a mouse hover menu but not with tap or keyboard is incomplete.

Content language and readable text

Set lang on the html element. Mark language changes inline when quoting another language so screen readers switch voices correctly.

Line length and spacing affect cognitive load. Extremely long lines of body text are harder for many readers. I keep article measure around 60–75 characters where the design allows.

Avoid justified text with awkward gaps on the web. Left-aligned body text remains the safest default for mixed languages and zoom levels.

Shipping culture beats heroics

The checklist only works if someone owns it. On my teams that is every frontend engineer for their PR, plus a periodic audit of top funnels with a screen reader.

Designers and engineers should share the same definition of done: keyboard works, name is announced, errors are clear, contrast passes. When that definition is in the ticket template, debates shrink.

You will not hit perfection on day one. You will stop shipping the same five bugs repeatedly. That is the win. Start with the checklist above on the next UI PR and expand coverage as the component library matures.

Accessibility is quality engineering with a human face. Treat it like performance budgets: measurable, enforceable, and part of normal delivery—not a special occasion.

Key takeaways

  • Bake keyboard, names, forms, and contrast into every UI PR—not a late sprint.
  • Prefer native HTML controls; add ARIA only when you must extend behavior.
  • Icon-only controls need aria-label; placeholders are not labels.
  • Tie error messages to inputs with aria-describedby and announce them.
  • Check contrast in the rendered page and honor prefers-reduced-motion.
  • Use a short PR checklist plus an automated scanner for regressions.

About the author

Ram — Founder & Editor, BudhiWorks. I build and ship production web apps — Node, React/Next.js, Postgres, and the boring infrastructure that keeps them online. BudhiWorks is where I publish the guides I wish I had when something broke at 2 a.m.

More about Ram · Contact

← MongoDB Aggregation Pipelines I Actually U… Next: Debugging Node.js Memory Leaks with Heap S… →