AI builders optimize for the preview — and a screen reader, a keyboard, and a contrast check are exactly what a preview can't show. Four things AI drops unless you ask, the prompt block that puts them back, and a three-minute check.
The reason an AI-built page fails accessibility is not carelessness. It is the feedback loop. The model writes markup, a preview renders, you look at the preview, you approve. Everything in that loop is visual. A screen reader, a keyboard-only path, and a contrast ratio are the three things the preview is structurally incapable of showing you — so they are the three things that quietly never get built.
You see a clean form with a nice hover state. Someone opens it with a screen reader and hears "button, button, button" with no idea what any of them do. Someone else navigates by keyboard and the focus ring disappears halfway down the page. A third person can't read the light-gray helper text at all. None of that was in the preview. That is the whole problem, and it is fixable in the prompt.
An AI builder is rewarded for matching what you can see. The prompt describes a layout and a feel; the preview confirms the layout and the feel. So the model spends its budget on spacing, color, and motion, because that is what gets graded. Accessibility lives one layer down, in markup the preview renders identically whether it is correct or not. A missing <label>, a 3:1 contrast ratio, a focus order that jumps around — the page still compiles, still looks polished, still demos fine.
That is why "the markup looks right" is not evidence of anything. The pretty shell and the accessible page are visually identical. An unguided model ships the shell because nothing in its loop ever penalizes it for the gap. You close the gap by naming the four things it drops, then forcing it to confirm it did them.
1. Labels a screen reader can read. AI loves placeholder text — gray hint text inside an input — and treats it as the label. It isn't. Placeholder text disappears the moment you type, and most screen readers don't announce it as the field's name. A real association is a <label for="email"> tied to <input id="email">, or an aria-label on the input. Without one, the field announces as "edit text, blank" — useless.
2. Text you can read. Models reach for low-contrast gray on white because it reads calm and modern. The WCAG floor for body text is a 4.5:1 contrast ratio against its background; a lot of AI-default helper text lands around 3:1 or worse. That is not a stylistic quibble — it is the line between readable and not for a real share of any audience.
3. A keyboard path you can see. Plenty of people never touch a mouse. Every interactive element — buttons, links, inputs, custom dropdowns — has to be reachable with Tab and operable with Enter or Space, in an order that matches the visual flow. And the focus has to be visible: a clear outline on the element you are on. AI frequently builds clickable <div>s a keyboard can't reach at all, or strips the focus outline in CSS because it looked "untidy."
4. Alt text on images. Every meaningful image needs an alt describing it; every decorative one needs an empty alt="" so the screen reader skips it cleanly. AI tends to emit <img> with no alt at all, which makes the screen reader read the filename aloud — "hero underscore final two dot png."
Built the accessible version and want a real person to open it before it goes wide? Drop the self-contained HTML and get an unguessable, private-by-default link. Accessibility lives in your markup; the link just delivers it — and the person you send it to needs no account.
Put this at the top of the session, or in your tool's system / custom-instructions slot, so it applies to every page instead of being something you remember once:
Build this as accessible HTML to WCAG 2.1 AA. Specifically: every form input gets a programmatically associated
<label>(oraria-label) — never rely on placeholder text as the label. Body and helper text must hit at least 4.5:1 contrast against its background. Every interactive element must be keyboard-reachable in logical Tab order with a visible:focus-visibleoutline — no clickable divs, nooutline: nonewithout a replacement. Every image gets analt(emptyalt=""if decorative). Use semantic landmarks —header,nav,main,footer— not generic divs. State which of these you applied.
That last sentence is the part that actually changes the output. The whole failure mode is a loop with no accessibility check in it — so you add one by hand. Asking the model to report what it did forces a pass it would otherwise skip and nod through. When it answers "added labels, used :focus-visible, contrast checked at 4.6:1," you have specific claims you can verify in the next three minutes instead of a vibe.
This block is tool-agnostic. It works the same dropped into Claude, a Cursor build, a Lovable app, or a v0 page — the defaults it corrects are shared across all of them, because they all share the same visual feedback loop.
The prompt gets you most of the way. The verification catches what slipped — and it runs on the local file or the preview, before anything ships.
Unplug the mouse and Tab. Press Tab from the top and walk through every control. Can you reach all of them? Can you always see which one you are on? Can you operate buttons with Enter or Space? If focus disappears or skips a control, that is a real failure you just found in thirty seconds.
Run the browser's built-in audit. Chrome's Lighthouse and the axe DevTools extension both run an accessibility pass that flags missing labels, contrast failures, and unlabeled images with the exact element. It won't catch everything — keyboard logic and alt-text quality still need a human — but it clears the mechanical gaps fast.
Listen for two minutes. Turn on VoiceOver (Cmd-F5 on Mac) or NVDA (free, Windows) and arrow through the page. You will hear immediately whether buttons announce a name and inputs announce a label, or whether it is a wall of "button, edit text, blank." Two minutes of listening tells you more than ten minutes of reading markup.
None of this is heavy, and that is the point. A <label>, an aria-label, an alt, a focus outline — they add bytes you would never notice and change nothing for a mouse user. If you are already prompting for lean, self-contained output, accessibility rides along at effectively zero weight.
An accessible page built this way is self-contained HTML: markup, styles, and any client-side script in one file. That serves cleanly from a static private-link host — accessibility intact, because it lives entirely in the HTML and CSS, not on a server. Drop the file, get a private link, send it. The person opening it needs no account, and the page announces itself correctly to whatever they are reading it with.
The honest line: if your page needs server-side work — processing a form into a database, real authentication, dynamic per-user content — that is a backend job, and Vercel or Netlify are the right tools for it. A static host serves the page; it does not run a server. But the accessibility work is identical either way. Labels, contrast, keyboard order, and alt text are properties of the HTML, so they come with the page wherever it ends up. Build them in once, verify in three minutes, and the page works for everyone who opens the link you send.
Prompt patterns that get Claude, ChatGPT, v0, Lovable, and Cursor to ship self-contained, lightweight, interactive HTML you can actually send as a link.
v0 and Lovable build React/Next projects, not single files — so "export" means a folder or a public deploy, neither of which is a quiet private link. Here's how to get a clean build and share the static slice privately.
Your AI built a working calculator. It runs in the chat preview and nowhere else. Here is how to get that interactive tool onto a real shareable link without touching a deploy command.
miinideck turns a single HTML file into an unguessable link with optional password and expiry. Default-private, never indexed.