How to Make an AI-Built App Accessible

Generated code looks fine and fails a screen reader instantly. Here is the short list of what breaks, why, and the prompts that stop it happening again.

Steve Jefferson
Steve Jefferson
Developer Advocate
14 August 20261 min read

An app built with AI will usually pass a casual look and fail a screen reader in the first ten seconds. The reason is mundane: models produce markup that matches the visual examples they saw most often, and the most common examples on the web are inaccessible. Clickable divs, icon buttons with no name, placeholder text standing in for labels, colour contrast tuned for a design screenshot.

None of this is hard to fix. It is just never in the prompt. Here is the order I would fix it in, ranked by how many users each change unblocks per hour of work.

1. Replace clickable divs with buttons

The single most common generated pattern, and the most damaging.

<div className="btn" onClick={submit}>Save</div>

A div is not focusable, does not respond to Enter or Space, and announces as nothing. Anyone navigating by keyboard cannot reach it at all.

<button type="button" onClick={submit}>Save</button>

That one substitution fixes focus, keyboard activation and announcement together. Search your codebase for onClick on a non-interactive element and fix every hit before doing anything else on this list.

2. Give every icon-only control a name

Generated UIs love bare icon buttons: a trash can, a pencil, a hamburger. Visually obvious, silently unlabelled.

<button aria-label="Delete invoice" onClick={remove}>
  <TrashIcon aria-hidden="true" />
</button>

Two parts matter. The accessible name goes on the button. The decorative icon inside gets hidden, so it is not announced twice or read as a meaningless graphic.

3. Label form fields properly

Placeholders are not labels. They vanish when the user types, they are frequently skipped by assistive tech, and they fail contrast requirements in almost every generated design.

<label htmlFor="email">Work email</label>
<input id="email" type="email" autoComplete="email" />

While you are there, add autocomplete attributes. They cost nothing and they help every user with a motor or memory impairment, which is a much larger group than most people assume.

4. Fix the heading order

AI-generated pages routinely jump from an h1 to an h3 because the design called for smaller text. Screen reader users navigate by headings the way sighted users skim, so a broken outline is a broken navigation system.

Read the headings alone, in order. If they do not describe the page structure sensibly, fix the levels and use CSS for the sizing.

5. Fix the tables and lists that are neither

Generated layouts frequently render tabular data as a grid of divs, because that is what the design mock looked like. Visually identical, structurally meaningless: a screen reader user gets a stream of disconnected values with no way to know which column a number belongs to.

<table>
  <caption>Monthly invoices</caption>
  <thead>
    <tr><th scope="col">Client</th><th scope="col">Amount</th></tr>
  </thead>
  <tbody>
    <tr><td>Acme</td><td>1,240</td></tr>
  </tbody>
</table>

The scope attributes are what associate each cell with its header. The caption is what tells a user what the table is before they enter it. Both are routinely omitted.

The same applies to lists. A stack of divs that looks like a list does not announce as one, so the user never hears "list, 6 items" and loses the ability to skip it. Use ul, ol and li.

6. Check contrast on the generated palette

Model-generated colour schemes tend to look elegant and land around 3:1. The requirement for normal text is 4.5:1.

The usual culprits: grey secondary text, placeholder text, disabled states, and white text on a mid-tone brand colour. Run the palette through any contrast checker once, adjust the three or four values that fail, and it stays fixed.

7. Make focus visible

A surprising number of generated stylesheets contain some version of this:

*:focus { outline: none; }

It exists because outlines look untidy in a screenshot. It makes keyboard navigation impossible, because the user cannot see where they are. Delete it. If the default ring is genuinely ugly, replace it rather than remove it.

:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

8. Announce dynamic changes

Anything that appears without a page load is invisible to a screen reader unless you say otherwise: toast notifications, validation errors, search results updating as you type.

<div role="status" aria-live="polite">{message}</div>

Use polite for confirmations and results. Reserve assertive for errors that stop the user proceeding. An app that announces every keystroke is worse than one that announces nothing.

9. Test with the keyboard, then with a screen reader

Ten minutes, no tooling. Put the mouse down and Tab through your main flow.

  • Can you reach every control?

  • Can you see where you are at all times?

  • Does the order match the visual layout?

  • Can you close a modal with Escape, and does focus return sensibly?

Then turn on the screen reader you already have. VoiceOver on macOS is Cmd+F5. Narrator on Windows is Ctrl+Windows+Enter. You are not trying to become an expert user. You are listening for controls that announce as "button" with no name, or as nothing at all.

10. Put it in the prompt, permanently

Fixing an app once is a Tuesday. Keeping it fixed means the rules live where the code is generated. Add them to your project instructions or your agents.md file so every future generation inherits them:

Accessibility rules for all generated UI:
- Interactive elements are <button> or <a>, never div or span with onClick
- Every icon-only control has aria-label; decorative icons are aria-hidden
- Every input has an associated <label>; placeholders are never labels
- Heading levels are sequential; size is controlled by CSS
- Text contrast is at least 4.5:1 against its background
- Never remove focus outlines; restyle with :focus-visible instead
- Dynamic status messages use role="status" with aria-live="polite"

That block is more effective than any amount of after-the-fact review, because it changes what gets written rather than what gets caught.

The two references worth keeping open

You do not need to read a standard end to end. You need two pages bookmarked.

The WCAG quick reference is the filterable list of success criteria, which is the fastest way to check whether a specific pattern you are unsure about has a defined requirement. The MDN ARIA documentation is the practical companion for the moments when a native element genuinely will not do the job.

One warning about ARIA, because generated code overuses it badly. Models reach for role and aria-* attributes where a plain HTML element already carries the behaviour. A role="button" on a div still needs you to add keyboard handling, focus management and Enter and Space activation by hand. An actual button has all of it. If you find yourself adding three ARIA attributes to make something behave like a standard element, you have chosen the wrong element.

Where this sits in your build

Accessibility work is cheapest immediately before launch and most expensive six months after, which puts it naturally alongside the rest of your pre-launch testing. If you are adding user accounts, do the form labelling pass then, because sign-up and sign-in are where inaccessible forms hurt most: a user who cannot complete registration never reaches anything else you built.

And if you are handing the project on later, note that accessible markup is one of the clearest signals of quality a reviewing developer will look for, which makes this list worth running before any handover.

Common questions

Do I need a full WCAG audit?

Not to start. The ten items above remove the large majority of practical barriers in a typical generated app. A formal audit makes sense when you have a legal obligation or an enterprise buyer asking for one.

Can I just ask the AI to make it accessible?

Partly. Models will fix what you point at and miss what you do not, because they cannot perceive the rendered result. Asking for an accessibility pass finds real issues. It does not replace tabbing through the app yourself.

Are automated checkers enough?

They catch roughly a third of real issues, which makes them worth running and dangerous to trust. Contrast failures, missing alt text and missing form labels are detected reliably. Whether your focus order makes sense, whether your button labels describe what actually happens, and whether a modal traps focus correctly are all judgement calls no tool makes for you. Run the checker, then still tab through the app.

What about alt text on generated images?

Models write alt text describing what a picture contains, which is right for photographs and wrong for functional images. An icon that means "delete" should be labelled delete, not "grey rubbish bin outline". A purely decorative image should have an empty alt attribute so it is skipped entirely rather than announced.

Which single fix matters most?

Replacing clickable divs with real buttons. It is the most common generated defect and it blocks keyboard users completely rather than merely inconveniencing them.

How did this land?

About the author

Steve Jefferson
Steve Jefferson

Developer Advocate

Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.

Share

Get the next post in your inbox

One email a month. Product updates, engineering posts, and the best of Built with Swarmz.

I agree to receive emails about AI building tips and Swarmz product news. Unsubscribe any time.