by A11yGuard (LX AI)
Vibe-coded UIs fail accessibility at two points scanners rarely catch together: contrast math and accessible names. A component can pass a color audit and still be unusable when its role is wrong or its name is empty. Both break again on regeneration, so the fix belongs in a checklist, not a one-off edit.
When a model invents a palette, it usually copies a mood board: soft slate text, pastel chips, translucent cards. That looks fine on a designer's monitor and fails the ratios in WCAG 2.2 the moment real content lands. Body text at Level AA needs a 4.5:1 contrast ratio against its background (success criterion 1.4.3). Large text can use 3:1. Generators do not run that math; they pick hex values that "feel secondary."
A second failure mode is the background you cannot see in the Figma export. Text over a hero image, a gradient wash, or a glassmorphism panel needs the rendered background, not the token named bg-surface. Sample the pixel under the glyph, or you will clear a token audit and still ship unreadable copy. Charts and data ink have the same trap; Smashing Magazine's write-up on chart contrast shows how standard thresholds change visual design choices without turning every chart into a rainbow (accessibility standards for chart design).
Success criterion 1.4.11 (Non-text Contrast) asks for 3:1 on UI component boundaries, graphical objects needed to understand content, and states such as focus indicators. A faint 1px border that looks "premium" often fails. A focus ring set to outline: 2px solid rgba(15,118,110,.35) fails even when the label text passes 4.5:1. Icon-only controls that rely on a light grey glyph against white are the same class of bug. Treat border, icon, and focus colors as first-class tokens with published ratios, not as decorative afterthoughts. The WCAG 2.2 Quick Reference is the fastest place to re-check which criteria apply at AA.
The durable fix is boring: define semantic color tokens (text-primary, text-muted, border-strong, focus-ring) with measured ratios against each allowed background, then forbid the model from inventing hex strings in component files. Put the forbid rule in the prompt and in CI. If regeneration can rewrite color: #94a3b8 into a button label, your "fix" was never a fix. Pair tokens with a CSS variable layer so dark mode and marketing themes inherit the same constraints.
Screen readers announce the accessible name of a control, not the SVG path data. An icon button that shows a trash glyph with no text content, no aria-label, and no associated <label> becomes "button" with no purpose. That is a naming failure, not a contrast failure, which is why color audits stay green while keyboard users stall.
The name computation order is defined by the accessibility tree: content text, labelled-by references, then author-supplied labels such as aria-label. Generators often emit the glyph and a Tailwind class and stop. They also invent duplicate names ("Close" on every dismiss control in a stack of modals) or names that describe the icon metaphor instead of the action ("trash can" instead of "Delete invoice"). Prefer action language that matches the visible affordance.
Regeneration wipes inline aria-label values that were added by hand because the model treats them as optional noise. Survive that by:
<div onClick> without a role and name packageWAI's fundamentals overview is still the best short primer on why names, roles, and states must travel together (WAI Accessibility Fundamentals).
Native HTML already exposes roles, keyboard behavior, and many states. A real <button> is focusable, activates on Enter and Space, and participates in forms. A <div role="button" tabindex="0"> that a model pasted still needs those key handlers, a pressed or disabled state when relevant, and an accessible name. Skip any of that and assistive tech announces a button that does nothing on Space. The ARIA Authoring Practices Guide (APG) exists for the cases where you truly need a custom widget; it is not a license to restyle a div into every control.
role="button" on an <a href>, or role="link" on a button that submits a formaria-label that duplicates visible text and confuses some AT combinationsaria-expanded="true" left behind after a client render toggles a panel shutrole="navigation" regions with no names, so skip lists become noisearia-live="polite" on every toast so the page chattersThe WAI-ARIA 1.2 recommendation describes the ontology of roles and properties; use it when you must extend beyond HTML, not as a default layer on every node (WAI-ARIA 1.2).
| Situation | Prefer | Avoid |
|---|---|---|
| Primary action | <button type="button"> | <div role="button"> without keys |
| Navigation to a URL | <a href> | Button that only calls router.push with no URL |
| Icon-only control | Visible text or stable aria-label from a map | Bare SVG with title missing |
| Custom combo box | APG pattern + keyboard map | Invented roles copied from a chat answer |
Focus order, visible focus, and skip links drop out of one-shot layouts. Criterion 2.4.7 requires a visible focus indicator. Criterion 2.4.11 (Focus Not Obscured — Minimum), new in WCAG 2.2, requires that sticky headers, cookie banners, and floating chat widgets do not fully hide the focused control. Tab the generated page top to bottom. Confirm order matches reading order. Confirm no control is stranded behind an overlay. Confirm the focus ring meets 1.4.11 against whatever sits behind it.
That pass catches what Lighthouse will not. Automated tools are a first filter; they are not a conformance certificate. For hiring and automated decision tools, U.S. guidance also reminds builders that AI features can create disability discrimination risks if interfaces exclude people who use assistive technology (ADA.gov AI guidance). Product UI is not exempt from that thinking just because a model wrote the JSX.
Because regeneration wipes manual patches, lasting control looks like infrastructure:
IconButton, TextField) that always emit name + focus stylesdiv-as-button without an explicit exception flagTreat generated markup as untrusted draft output. Review it the same way you review a junior PR that arrived from a weekend hackathon: assume missing names, assume theatrical ARIA, assume pastel text.
Use this order when a bug report lands:
Page-level ARIA patches are the least durable. They are also the ones vibe-coding sessions love to emit. Push fixes upward.
This article is not a certificate of WCAG conformance for your product. It is not legal advice, and it does not claim that running A11yGuard (or any scanner) makes a site "ADA compliant" or "100% accessible." Automated contrast and ARIA checks catch measurable failures; they miss judgment calls about reading order, drag alternatives, and whether a name is understandable. We do not invent customer logos, conversion lifts, or "never miss a contrast bug" guarantees. The honest product role is a fast first pass plus a checklist you still own.
Contrast math and accessible names fail independently and regenerate together. Lock tokens, prefer native controls, verify ARIA against APG when you must customize, and keep a keyboard pass in the release ritual. Use the references below when a claim about a ratio or a criterion is in dispute — the spec text wins over a chat model's memory.
Color audit checks the text ratio. It does not check whether the control has an accessible name, a visible focus state, or a keyboard handler. Those gaps are common in generated markup and are not contrast problems.
No. A role="button" div still needs tabindex, Enter/Space key handlers, and an accessible name. A native <button> provides all three. Use the native element unless you have a specific reason not to.
Lock color and spacing tokens in the design system so the model cannot invent values, and run an automated accessibility gate in CI. Treat generated UI as a draft and review it the same way you review untrusted output.
Criterion 1.4.11 requires UI component boundaries, icons, and focus indicators to reach 3:1 against adjacent colors. It is separate from the 4.5:1 text rule and is frequently missed by generated themes.
Try the product: A11yGuard
2026-09-16 · primary sources only · no fabricated traffic metrics