← Back to Guides
7 min readIntermediate
Share

Accessibility Testing for Vibecoded Apps

AI-generated UI code looks right and fails silently for keyboard and screen-reader users. Here's how to catch that before you ship, not after.

Accessibility Testing for Vibecoded Apps

Ask an AI assistant for a modal, a dropdown, or a custom button, and you'll get something that looks correct in the browser. Click it with a mouse and it works. Tab to it with a keyboard, or load it with a screen reader, and it often doesn't — no focus trap, no role, no visible focus ring, a <div onClick> standing in for a <button>. The failure is invisible unless you specifically go looking for it.

This is a systemic gap, not a one-off mistake. Models are trained overwhelmingly on code that renders correctly, and visual correctness is what gets reinforced. Keyboard and assistive-tech correctness is a different axis entirely, and prompts rarely ask for it explicitly. So it's on you to check.

The four things that break most often

1. Interactive elements built from <div>s. A <div onClick={...}> has no keyboard affordance, no focus state, and no semantic role — a screen reader announces it as nothing at all. Grep your codebase for onClick and check the parent element's tag. If it's div or span and there's no role + tabIndex + onKeyDown alongside it, it's mouse-only.

2. outline: none without a replacement. AI-generated form styling loves focus:outline-none because the default browser outline "looks ugly" against a custom design. Removing it without adding focus-visible:ring-* (or an equivalent) makes the page untabbable in practice — sighted keyboard users lose all positional feedback.

3. Modals and dialogs missing semantics. A lightbox or command palette needs role="dialog", aria-modal="true", an Escape handler, and a focus trap that returns focus to the trigger element on close. Ask for "a modal" and you'll usually get the visual box; you have to ask for the interaction contract separately, or add it yourself.

4. Images and icons with no alt text, or the wrong alt text. Decorative icons need alt="" (so screen readers skip them); meaningful images need a description of content, not appearance — alt="Bar chart showing signups up 40% in March", not alt="chart".

A five-minute manual pass

Automated tools catch maybe a third of real accessibility issues. Before you ship a new page or component, do this by hand:

  1. Unplug your mouse. Tab through the whole page. Every interactive element should get a visible focus ring, in the order you'd expect. If focus disappears, jumps somewhere weird, or lands on something that isn't actually interactive, that's a bug.
  2. Trigger every modal/dropdown/menu with only the keyboard. Open it with Enter or Space, close it with Escape, confirm focus goes back to where you triggered it from.
  3. Zoom to 200%. Layouts that assume a fixed viewport often clip text or overlap elements. This also flags containers with overflow: hidden hiding content instead of wrapping it.
  4. Toggle prefers-reduced-motion in your OS settings and reload. Anything that still animates aggressively — auto-cycling carousels, parasitic hover transforms, infinite background loops — needs a static fallback.

Automated tools worth wiring in

  • axe DevTools (browser extension) — run it on every page during review. It catches missing labels, contrast failures, and invalid ARIA in seconds.
  • eslint-plugin-jsx-a11y — catches the onClick-on-a-div and missing-alt problems at write time, before the PR even opens. If you're using an AI code tool to generate components, running its output through this linter immediately is the single highest-leverage check you can add.
  • Lighthouse's Accessibility score — not comprehensive, but a quick regression signal in CI. A sudden drop from 100 to 80 tells you something changed even if you don't yet know what.

Fixing focus rings without breaking your design

The instinct to strip outline: none usually comes from the default outline clashing with a dark theme or a rounded corner. The fix isn't removing it — it's replacing it:

button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

:focus-visible (as opposed to plain :focus) only fires for keyboard navigation, not mouse clicks — so you get a clean visual for mouse users and a real focus indicator for keyboard users, in one rule.

The takeaway

Accessibility bugs in AI-generated code aren't rare edge cases — they're the default output, because the model has no way to know your page needs to work without a mouse unless you tell it. Treat "keyboard operable" and "screen-reader sane" as requirements you state up front, and verify with a five-minute manual pass before every ship, the same way you'd check a page renders correctly on mobile.

Stay in the flow

Get vibecoding tips, new tool announcements, and guides delivered to your inbox.

No spam, unsubscribe anytime.