Web Development

Five Accessibility Fixes Teams Miss Most Often (With Code)

Five accessibility issues that show up in nearly every code review we do, each with the broken pattern we actually see and the fix we ask for.

By Aissam Ait Ahmed Web Development 0 comments

Why these five keep showing up in review

We review a lot of pull requests, and the same handful of accessibility misses show up regardless of who wrote the code or how experienced they are. None of these require a screen reader deep-dive or a full WCAG audit to catch — they're pattern-level mistakes that a five-minute review checklist would flag, if teams actually had one. Here are the five we flag most often, each with the broken version we actually see and the fix we ask for.

1. Focus styles removed and never replaced

The single most common offender: someone doesn't like the default browser focus ring, so they remove it globally without replacing it with anything.

/* Broken — outline removed, nothing takes its place */
.btn:focus {
  outline: none;
}

This makes the site effectively unusable for anyone navigating by keyboard, because there's no visual indicator of where focus currently is. The fix is :focus-visible, which lets you keep the clean look for mouse users while still showing a clear indicator for keyboard users, since :focus-visible only fires for keyboard (and other non-pointer) focus in modern browsers:

.btn:focus-visible {
  outline: 2px solid #1a56db;
  outline-offset: 2px;
}

.btn:focus:not(:focus-visible) {
  outline: none;
}

That second rule matters — without it, some browsers will still show the default outline on mouse click in addition to your focus-visible style, which is the exact "ugly outline" problem someone was trying to avoid in the first place.

2. Inputs with no programmatically associated label

A visible label sitting next to an input isn't enough on its own — a screen reader user needs the label programmatically tied to the field, or they'll hear "edit text" with no idea what it's for.

<!-- Broken — visually next to the input, not actually associated -->
<label>Email address</label>
<input type="email" name="email" placeholder="you@example.com">
<!-- Fixed — for/id pair ties the label to the field -->
<label for="email">Email address</label>
<input type="email" id="email" name="email" placeholder="you@example.com">

This is a two-second fix that we still see weekly, usually on hastily added form fields where a placeholder was mistaken for a label. Placeholders disappear the moment someone starts typing and generally have worse contrast than real label text, so they're not a substitute even visually, let alone for assistive tech.

3. "Disabled-looking" buttons that aren't actually disabled

Pending or unavailable actions often get styled to look disabled — greyed out, low contrast — while staying fully clickable, sometimes intentionally (to trigger a tooltip explaining why) and sometimes by accident.

/* Broken — low contrast against white, and no state communicated to assistive tech */
.btn--pending {
  color: #ffffff;
  background: #cbd5e1;
}

Two separate problems live in that one rule: the contrast ratio fails WCAG AA for text, and a screen reader has no idea this button isn't in its normal state. If the button is genuinely meant to still be interactive (say, tappable to explain why an action is unavailable), use aria-disabled rather than the disabled attribute, and fix the contrast so low-vision users can actually read it:

.btn--pending {
  color: #334155;
  background: #cbd5e1;
  cursor: not-allowed;
}
<button class="btn btn--pending" aria-disabled="true">Submitting…</button>

If it should genuinely be unclickable, use the real disabled attribute instead — that removes it from the tab order and announces its state correctly on its own, and you don't need aria-disabled at all in that case.

4. Clickable divs standing in for links and buttons

Wrapping a whole card in a div with an onclick handler is an easy trap, especially once a design calls for "the whole card is clickable."

<!-- Broken — not focusable, not in the tab order, no keyboard activation -->
<div class="card" onclick="openTool()">
  <h3>Word Counter</h3>
</div>

A div has no default keyboard interaction and no semantic role, so keyboard users can't reach it and screen readers announce nothing useful. If the card genuinely navigates somewhere, the fix is almost always to just make it a link:

<a class="card" href="/word-counter">
  <h3>Word Counter</h3>
</a>

If it triggers an in-page action instead of navigation, use a real <button> element rather than reaching for role="button" and a pile of manually-wired keyboard handlers on a div — the native element gives you focusability, keyboard activation, and correct semantics for free, and it's genuinely less code than the div version once you account for what the div version needs to fake.

5. Alt text that doesn't distinguish decorative from meaningful images

Teams tend to swing to one of two extremes: alt text on every single image regardless of whether it conveys anything, or alt text skipped entirely because "we'll get to it." Both create noise or gaps for screen reader users, who hear every alt attribute read aloud in sequence.

<!-- Decorative — contributes nothing to the content, empty alt so it's skipped -->
<img src="/images/divider.svg" alt="">

<!-- Meaningful — conveys information, needs a real description -->
<img src="/images/chart-q3-revenue.png" alt="Q3 revenue grew compared to Q2, driven mostly by renewals">

An empty alt="" attribute is a deliberate, valid signal that tells assistive tech to skip the image — it's different from omitting the alt attribute entirely, which some screen readers will instead read out the image filename for, which is worse than either extreme. For meaningful images, describe what the image conveys, not what it visually contains — "chart showing Q3 revenue growth driven by renewals" is more useful than "bar chart with blue bars." Keep it concise; if you're drafting longer descriptive alt text for a complex infographic, running the draft through a word counter is a quick sanity check against writing an alt attribute so long it becomes its own accessibility problem.

Why these five specifically

  • They're all detectable in code review without specialized tooling — no screen reader session required to catch them.
  • They tend to be systemic rather than one-off — a missing focus-visible style or an unlabeled input pattern usually repeats across every component that copied the original.
  • They affect real proportions of users, not edge cases — keyboard navigation and low vision are common enough that fixing these has a genuinely wide blast radius.

A five-minute keyboard-only pass catches most of this without any tooling

Before reaching for an automated scanner, the single fastest way to catch four of these five issues is to unplug the mouse — figuratively or literally — and try to complete the page's main task using only Tab, Shift+Tab, Enter, and Space. It takes a few minutes per page and needs no installed tooling at all.

  • Tab through the page from the top. Does a visible focus indicator appear on every interactive element, in a sensible order? If focus disappears or jumps somewhere unexpected, that's issue #1 or a related tab-order bug.
  • Try to reach every form field this way and confirm each one's label is announced correctly by checking the accessible name in your browser's dev tools accessibility panel, not just glancing at the visible layout.
  • Try to activate every clickable-looking element with Enter or Space alone, no mouse. Anything that doesn't respond is almost certainly a clickable div rather than a real button or link — issue #4.
  • Zoom the page to 200% and check whether disabled-looking buttons are still legible at a glance, which tends to surface the contrast problem in issue #3 faster than reading a hex value would.
  • Try navigating a card-based layout (like a grid of tool listings) with Tab alone and count how many key presses it takes to reach the third or fourth card — a surprisingly high count is often a sign that decorative elements are sitting in the tab order and need tabindex="-1" or shouldn't be focusable in the first place.

This pass won't catch everything — alt text quality specifically still needs a human judgment call about what an image actually conveys, which is issue #5 and the one keyboard-only testing can't verify on its own — but it catches the other four reliably, and it's fast enough to run on every PR touching interactive UI without slowing anyone down meaningfully.

Building this into review instead of fixing it after the fact

The teams that stop reintroducing these bugs are the ones who add them to a PR checklist or a lint rule rather than relying on a one-time audit. eslint-plugin-jsx-a11y and equivalent Blade/HTML linting catch some of these automatically; the rest — like the disabled-looking button contrast issue — still need a human eye during review, at least until your design system enforces contrast ratios as tokens rather than one-off hex values.

Some of these fixes also interact with layout work — a focus ring on a card component behaves differently once that card is being resized by container queries rather than fixed breakpoints, since the ring needs to stay visible and correctly offset at every size the card can render at, not just the sizes you happened to test at your default viewport width. It's a good example of why accessibility review can't be a one-time sign-off before launch — a component that passed review at one size can silently regress the moment its layout logic changes months later, for reasons that had nothing to do with accessibility at the time the change was made.

Comments

Join the conversation on this article.

Comments are rendered server-side so the discussion stays visible to readers without relying on a separate widget or client-side app.

No comments yet.

Be the first visitor to add a thoughtful comment on this article.

Leave a comment

Share a useful thought, question, or response.

Be constructive, stay on topic, and avoid posting personal or sensitive information.

Back to Blog More in Web Development Free Resources Explore Tools