Dashboard

Getting an AI Coding Agent to Write Accessible Code

Interactive divs, unlabelled inputs, colour-only state, and dialogs that lose focus. Four defects, four rules, and the CI checks that make them stick.

Carlo Zuercher
Carlo Zuercher
Staff Engineer, Platform
6 September 20261 min read

An AI coding agent will produce accessible code when you make accessibility a constraint it can check, and it will produce inaccessible code every other time. Left alone, agents ship the same four defects with remarkable consistency: a div pretending to be a button, an input with no programmatic label, state communicated only by colour, and a modal that traps or loses keyboard focus. All four pass a visual review. All four fail a real user.

This is how to stop shipping them, in rough order of return on effort. It assumes you already have an agent in your workflow; if you are still choosing, AI coding tools covers the landscape.

Why agents default to inaccessible markup

Training data is full of production code that was never audited, and a model optimises for markup that renders correctly, not markup that announces itself correctly. Nothing in "build a settings panel" implies a screen reader will read it.

There is a second, subtler reason. Accessible markup is more verbose, and agents that have been nudged toward concise output will reach for <div onClick> over a <button> with the same reliability that they reach for a shorter variable name. If your project instructions reward brevity, you are quietly rewarding this.

The four defects, and the instruction that prevents each

1. Interactive divs

The most common defect by a distance. A <div> with a click handler is invisible to keyboard and screen reader users: no focus, no Enter or Space activation, no role announced.

The instruction that fixes it, in your agent's project file:

Any element a user can activate MUST be a <button> or an <a href>.
Never attach onClick to a div, span, or li. If styling requires a
neutral container, use a <button> with the styles reset, not a div
with a handler.

2. Inputs without programmatic labels

Placeholder text is not a label. It disappears on focus, is not reliably announced, and fails at low vision zoom levels.

Every form control MUST have a programmatic label: a <label for>,
or aria-label when no visible label exists. Placeholder is never a
substitute. Icon-only buttons MUST have an aria-label describing
the action, not the icon.

3. Colour-only state

A red border marking an invalid field, a green dot for "online", a highlighted row for "selected". Each communicates state through a channel that a colourblind user or a screen reader user does not receive.

State must be conveyed by at least two channels. Colour may be one
of them, never the only one. Error states require associated text
plus aria-invalid and aria-describedby pointing at the message.
Selection requires aria-selected or aria-checked, not a background
colour alone.

4. Focus management in dialogs

Agents build dialogs that render. They rarely build dialogs that move focus in on open, trap it while open, restore it to the trigger on close, and close on Escape. Four behaviours, and most generated modals have none of them.

Any dialog, drawer, or popover MUST: move focus to the dialog on
open, trap focus within it, return focus to the triggering element
on close, and close on Escape. Use the native <dialog> element or
a maintained headless library. Do not hand-roll focus trapping.

Put these in the project file, not the chat

Instructions typed into a conversation last until the context rolls over. Instructions in the file your agent reads at the start of every session last as long as the repository. If you have not set one up, writing an AGENTS.md file is the mechanism, and accessibility rules are among the highest-value things to put in it, because they are exactly the constraints a fresh session will otherwise forget.

Keep the wording imperative and checkable. "Follow accessibility best practice" produces nothing. "Never attach onClick to a div" produces a different file.

Make the check automated, or it will not happen

Written rules decay. A failing build does not.

Add an automated accessibility linter to the project and to CI. For React and similar frameworks, eslint-plugin-jsx-a11y catches interactive divs, missing labels and several role errors at lint time, which means the agent sees the error in its own tool output and fixes it without you asking. For rendered-page checks, axe-core runs against a real DOM and catches contrast and structural problems that static analysis cannot, and its published rule list doubles as a decent checklist on its own.

Wire both into the same command your agent runs after editing. An agent that can see a failing check will iterate against it; an agent that cannot will assure you the code is accessible. This is the general lesson from running an AI coding agent in CI: the feedback loop does the teaching, not the instruction.

What automation still misses

Roughly a third to a half of accessibility issues are not machine-detectable, and the ones that survive are the ones that matter most:

Issue

Why a linter cannot see it

Alt text that is technically present but useless

"image1.png" passes every check

Heading structure that does not match the visual hierarchy

Valid HTML, wrong document outline

Focus order that jumps around the page

Each element is focusable, the sequence is nonsense

Error messages that describe the rule, not the fix

Correctly associated, unhelpfully worded

Motion and animation without a reduced-motion path

Media query absence is not an error

For the first row specifically, a model is genuinely good at drafting descriptions when asked properly, and prompting AI to write alt text is worth the five minutes it takes to get right once.

The rest come down to keyboard testing. Tab through the feature you just built, without touching the mouse, and try to complete the task. It takes two minutes per feature and it finds more than any tool. Do it before the review, not after, and make it part of reviewing AI-generated code before you ship it rather than a separate ritual you will skip when busy.

If you are building the whole app with AI rather than editing an existing one, the same rules apply earlier and cheaper, and making an AI-built app accessible covers that starting point.

FAQ

Which accessibility standard should I target?

WCAG 2.2 level AA is the common legal and procurement baseline. You do not need to read the specification to hit most of it, but you do need the automated checks and the keyboard pass.

Will telling the agent to "be accessible" work?

No. Vague instructions produce vague compliance. Named rules with a checkable condition produce code that passes.

Do these rules slow the agent down?

Marginally, and they save far more time than they cost, because retrofitting focus management and label associations across a finished feature is considerably slower than getting them right during the build.

What about component libraries that handle this for me?

A well-maintained accessible component library removes most of these four defects, which is a strong argument for telling your agent which library to use rather than letting it hand-roll a dialog.

How did this land?

About the author

Carlo Zuercher
Carlo Zuercher

Staff Engineer, Platform

Carlo works on the platform that turns prompts into running apps. He writes the engineering deep dives and the changelog notes worth reading.

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.