AI generates inaccessible interfaces: how to check and fix it automatically
Main chat
A chat for vibe coders: news, guides, live cases, marketplace, and finding executors.
Open the inspector in the browser and walk Tab on the last screen that the AI assistant generated for you. Most likely, the focus jumps in the wrong direction, half of the buttons are div with onClick, and the modular is closed only by the mouse. Visually everything is beautiful. Functionally - a wall for anyone who does not use a mouse and a sighted look.
AI writes interfaces quickly. It does not make accessibility by default - it copies what it has been taught, and the training data is full of div-s instead of buttons, pictures without alt and contrasts on the edge of readability. The more code a team generates through assistants, the worse the average level of accessibility in a project becomes. And this is not seen in the designer's review, but in the user with the screen reader.
The good news is that most of these problems can be caught automatically. Bad – no one sets up the pipeline until a complaint or lawsuit arrives.
Why AI-generated interfaces break accessibility
It's not the model's malice. It's about how it works.
**Model optimizes visual output. ** If the prompt sounds like “make a beautiful product card”, the model will make beautiful. Semantics, focus, contrast are not part of the reward.
Training data is dirty. Most of the open front-end code is written without looking at a11y. The model learns from the median and the median is bad.
Prompts rarely contain accessibility requirements. The designer writes "hero section with CTA" rather than "hero section with CTA, contrast AA, focus states, semantic button, aria-label if the icon is one.".
**The screenshot in Figma MCP does not contain semantics. When you throw the layout into the assistant through the MCP server, it sees the pixels and layers, not the role of the element. "It's a button or a link," he decides by eye.
Typical patterns I catch on a review
divandspanwith a click handler instead ofbuttonora- Icon buttons without
aria-label– the screen reader reads the “button”, and all - Pictures and illustrations with empty or junk
alt(“image”, “picture1”) - Colored text on a color plate with a contrast below 3:1
- Forms without
label, with a placeholder as a signature - Modals without trap focus and no return of focus to trigger
- Error states transmitted only by color, without icon or text
- Custom selectors and tabs without ARIA roles and keyboard navigation
- Animations without
prefers-reduced-motion
It's not exotic. That's what flies out of an assistant 8 times out of 10, to say the least.
What should be in the piplin, not in the designer’s head
The main mistake is to think that accessibility catches the eyes of the review. Eyes get tired, reviews are missed, new screens are generated faster than the team can watch them. We need automatic locks.
Minimum Pipeline Verification
- Linder in IDE.
eslint-plugin-jsx-a11yor equivalent for your stack.div onClick, missingalt, incorrect ARIA attributes right at the time of writing the code. - Component Unit Testing.
jest-axeorvitest-axeon top of component tests. Each new component is run through the axe-core and falls if there is a violation. - E2E-check key screens. Playwright +
@axe-core/playwrighton Smoke scenarios: main, login, checkout, main product desktop. Verification is turned into CI for every PR. - Visual contrast. Storybook with addon a11y, or a separate step in CI, which checks contrasts by design tokens.
- Manual check once in a sprint. Tab by float, VoiceOver or NVDA in a key script. The machine catches 40-60% of problems, the rest is only a live bypass.
If the Pipeline does not have at least the first three points, AI will steadily pass you by inaccessible code, and you will learn about it from the user.
Anti-pattern: "Availability added later"
In the team, it sounds like “let’s start first, then fix.” In practice, “later” means refactoring half of the component library, because the div buttons spread throughout the product and you cannot change them at once – too many places break. It's cheaper to put a lock at the entrance.
How to Formulate Prompts So AI Does Not Generate Garbage
Prompt is TK. If there are no accessibility requirements in the TK, they will not be as a result.
Prompt pattern for component generation
- Component assignment and role (action button, link, switch)
- Semantic tag that is expected to be released
- Behavior from the keyboard: what keys, what order of focus
- ARIA attributes if the component is not native
- Conditions: hover, focus-visible, active, disabled, loading, error
- Contrast: Minimum AA for text, AA Large or 3:1 for interactive boundaries
- Behavior in
prefers-reduced-motion
Sounds boring. But once put into a system pump or snippet, this block saves hours of review.
Questions I Ask Myself Before an AI-Generated Screen
- Can I run the entire script from the keyboard without touching the mouse?
- Is there a focus on each interactive element?
- If you turn off the color, is it clear where the error is, where the success is, where the active state is?
- What will the screen reader read on the icon button?
- What happens to the modular if you press Esc and Tab inside it?
- Does animation respect the “reduce movement” system setting?
If the answer to any question is “don’t know,” the screen isn’t ready, no matter how beautiful it looks in Figma.
Segment summary. AI doesn’t specifically break accessibility—it just optimizes the wrong thing. As long as there are no automatic gateways in the Pipeline and there are no requirements for semantics, focus and contrast, each new generated screen adds to the debt. Next, we will analyze what checks should be put in CI, how to read their reports and what to do with the violations found, without turning the work into an endless bugfix.
CI: what checks and where to put them
When I say, “Put a11y checks in the CI,” the answer is, “We already have a linter.” Linter is the first line, she catches the syntax. CI should capture behavior and final markup.
Levels of inspections by value
- Pre-commit (seconds). Linter to modified files.
divwithonClickwithout a role,imgwithoutalt, unknown ARIA attributes. - PR-check on components (minutes). Unit tests with axe are run on a storibuk or on test bindings of components. They fall on a specific component, not somewhere in the project.
- PR checks on pages (minutes to tens of minutes). Playwright picks up the app, walks on key routes, runs axe and checks for focus-trap, Tab order, skip-link presence.
- Night run (watch). Complete product bypass with different user roles, different themes, locales and a 200% scale. This includes visual regression of focus styles.
It is important not to put everything in one stage. If a11y tests hang for 20 minutes on each PR, they will start turning them off. Quick checks for each committ, heavy checks at night.
What should block merz, and what should only warn
| Уровень | Реакция CI |
|---|---|
| Critical (нет alt, нет label у инпута, контраст ниже 3:1 у текста, ловушка фокуса) | Блок мержа |
| Serious (неверный ARIA, нет focus-visible, ошибки порядка заголовков) | Блок мержа |
| Moderate (декоративные элементы в табордере, избыточный ARIA) | Предупреждение |
| Minor (стилистические замечания) | Предупреждение |
Without this division, the team either ignores the yellow report or hates the red report.
How to Read a Report Without Drowning
The first run of axe on a live product usually results in hundreds of violations. This is normal and demoralizing at the same time. If you open the report and go fix it from top to bottom, you're stuck in one module for a week.
Analysis algorithm
- Group violations by rule, not by page. One rule often breaks down in one component, which is built into fifty places.
- Find the source. In nine cases out of ten it is the basic component in the design system:
Button,IconButton,Modal,Tabs,Tooltip. - Fix the root. One fix in
IconButtoncovers hundreds ofbutton-nameviolations. - Only after that, go for “leaf” violations – where the problem is in a particular page, not in the system.
Anti-pattern: "Fix page by page"
The team takes the top 10 pages of traffic and rules by hand. After a month, the AI assistant will generate the eleventh and bring exactly the same errors. You need to fix components and prompts, not pages.
What to do with the fact that the machine does not catch
Axe and his brethren honestly say they find 30-50% of real problems. The rest is about meaning, not markup.
Scenarios that only live bypasses
- ** Focus logic after action.** Deleted the row in the table - the focus flew to
body. Technically clean, in fact, the screen reader is lost. - Reading order that does not match visual. The card is visually read from left to right, in the DOM - vice versa. axe is satisfied, the screen reader user is not.
- Icon buttons with a "understandable" label.
aria-label="Действие"is tested, but does not answer the question "what action". - Dynamic content without an ad. Toast appeared, disappeared - the screen reader said nothing because there is no
aria-live. - States transmitted only by motion. The clue appears on the hover and disappears after a second. The keyboard and touch are past.
Designer's checklist right in Figma
- Every interactive element in the layout has a focus state, not just a hover
- Icon buttons have a signature for the screen reader - I write it next to the frame comment
- The prototype has a Tab order consistent with the visual
- In the modal speck it is indicated: where the focus flies when opened, where it returns when closed, what does Esc
- Contrast checked not only for the main background, but also for hover/active/disabled
- For animations, it is noted what happens with
prefers-reduced-motion: reduce
This is not "extra work." It’s something that you’ll discuss with the developer anyway – better in advance, in the layout.
Questions for a pair review design + front
- Where does this component live in the system and what are its ARIA contracts?
- What happens if a user comes here from a screen reader without seeing the screen?
- What states do we forget to draw - loading, empty, error, no-permission?
- If AI generates a similar component tomorrow, what will CI fall on?
The last question is the most useful. He translates the conversation from "found the bug" to "closed the bug class.".
Short Segment Summary. Accessibility is not a heroic page sprint. It is based on three things: checking different costs in different stages of CI, parsing reports through the components of the design system, and a design specification in which the states, focus and behavior of the screen reader are described before the assistant generates the first line of code. Next, we’ll explore how to build this into a design system and work with AI tools like Figma MCP, so that new screens are born available.
Accessibility as a Design System Contract
If AI generates an interface that breaks accessibility, it’s not AI that’s to blame. The fault is a component contract that does not prohibit it. A design system is a place where rules must be so rigid that they cannot be broken by accident.
What does "available under contract" mean
The IconButton component, in which the label props is mandatory and TypeScript drops without it, is a contract. The component that the aria-label, aria-expanded, aria-controls itself throws is a contract. A storybook in which each component has an a11y tab with an axe run is a contract.
An AI assistant trained in your code will begin to repeat what it sees. If the codebase has one hundred IconButton without a signature, it will generate one hundred first. If they are zero, because otherwise the build falls, it will generate with a signature.
The minimum set of things that should live in the system
- Components with mandatory availability props at the type level
- Storybook with axe addon and file mode on critical rules
- Lint rules: ban
divwithonClick, ban positivetabindex, ban images withoutalt - Snapshot ARIA attribute tests for key interactive components
- Documentation of states: focus, hover, active, disabled, loading, error - in one place, next to the component
Working with AI Assistants and Figma MCP
When a designer pulls a layout through MCP into Cursor or Claude Code and the assistant turns the frame into JSX, the most expensive mistake occurs: the context is cut off. AI sees the layout, but does not see the specification of focus, screen reader behavior, and states.
What to put in the context of an assistant
- File with project accessibility rules – short, one page, without water
- Links to the basic components of the design system, not to third-party libraries
- Behavior description: “Modalus opens → focus on the first interactive element → Esc closes → focus returns to trigger”
- Prohibition of the invention of own semantics where there is a finished component
Anti-pattern: "AI will figure it out for itself"
The "make beautiful and accessible" prompt doesn't work. The assistant will generate what is more common in his training: div with role="button", an icon without a signature, a modular without a focus trap. It's not malice, it's the median of the Internet.
The opposite works: give the assistant a narrow frame. “Use Button from @/ui, label required, icon via icon props.” In this mode, AI has less freedom — and fewer ways to break accessibility.
Scenario: New screen via MCP in an hour
- The designer at Figma tags each interactive node with a component from the system and a signature for the screen reader in the layer description
- Assistant pulls frame, sees mapping on components, generates JSX strictly on them
- Pre-commit runs lint and axe on the storibuk affected components
- CI on PR runs axe on key pages and compares to the baseline
- The reviewer does not look at colors, but at the states and behavior of the keyboard
If at least one step fell, the screen will come to the product with a regression, and in a month no one will remember who generated it.
How to explain this to the team and management
Accessibility doesn’t sell well with the words “it’s right.” It sells well through risk, speed and cost.
Arguments that work
- Cost of remodeling. Fixing the component now is an hour. Fix a hundred screens in six months - sprint.
- Legal risk. For public products and the public sector, this would not be a good thing.
- Generation speed. AI-tools accelerate the release of screens at times. Without a contract, they also speed up the release of bugs.
- **Strong engineers and designers read your system. If it has
div onClick, you can see it right away.
Questions for the production review
- What percentage of users do we lose on keyboard navigation and don’t know about it?
- What happens to the metrics if the flow becomes passable for the screen reader?
- Which component is most commonly generated by an assistant and how well does it fit the contract?
- What do we add to CI so that the next regression does not go into sales?
Segment summary. AI doesn’t make interfaces inaccessible—it reflects what’s already in the codebase and design system. Therefore, the work is not with a model, but with a contract: types, casts, storibook, MCP framework, description of states in the layout. Next, let’s look at how to put all this together into one workflow that can withstand the speed of AI generation and doesn’t turn accessibility into a separate “someday later” project.
The final checklist: what should be in the project
This list is not the ideal, but the minimum below which AI generation begins to flow into the product. If one line is not closed, regression is a matter of weeks.
Design system
- Each interactive component has a focus, hover, active, disabled, loading, error state
- Button, link, checkbox, radio, select, input – components of the system, not local wrappers
- The button icon requires
aria-labelvia props type, not optionally - Editor, Popap, Tultip, Toast - one component per project, not five
Code and tools
- ESLint with
jsx-a11yin error mode on critical rules - Storybook with axe addon, build falls on violations of the level of serious and critical
- Snapshot or integration tests for ARIA attributes of key components
- Pre-commit Hook Drives Lint Through Modified Files
- CI runs axe on the list of key pages and keeps the baseline
AI and MCP
- In the context of the assistant is a short file of accessibility rules
- In the layout, each interactive node is marked with a design system component
- Prompts contain an explicit ban on
divwithonClickand their own semantics - The generated code passes the same lines and tests as the manual code
Anti-patterns that occur most often
“Let’s make accessibility a separate sprint.”
It never comes. After a quarter, the codebase grows, and the sprint turns into six months. Availability is repaired only incrementally, at the time of working with the component.
"We have a design system."
The system exists, and there is no contract to use it. The half-team assembles screens from components, the half-team assembles screens from naked tags, because “it’s faster.” AI repeats this distribution in the same proportion.
"Nobody is using a screen reader."
In metrics, this is not visible because such users leave silently after the first screen. They don’t write in sapport either – writing is inconvenient. No complaints equals no problem.
“AI generated, then we will correct”
“Then” means the next refactoring that won’t happen. The generated code goes into the product, is copied to neighboring screens, becomes a new model for AI in the next task. The cycle is closing.
Add aria-label wherever in doubt
Excess ARIA attributes break the screen reader more than their absence. role="button" on the native button, duplicates aria-label over the visible text, aria-hidden on the interactive element - frequent gifts from the assistant. Better native semantics without ARIA than ARIA over it.
Questions for PR review with AI-generated code
These questions should be kept in mind while reading diff. Half of them close in a minute if the answer is no.
- Are all interactive elements native tags or system components?
- Is it possible to fly only with the keyboard without losing focus?
- Is each form field associated with the label via
for/idor wrapper? - Icons without text have an accessible name? Decorative -
aria-hidden? - Is the error state available to the screen reader, not just highlighted in red?
- The modulator catches the focus, closes on Esc and returns the focus to the trigger?
- Dynamic changes are announced through
aria-live, where appropriate? - The contrast between text and interactive boundaries runs through design tokens rather than by eye?
What to do on Monday morning
Don't try to fix it all at once. Take the one component that the assistant most often generates – usually a button or input field. Describe his contract, close with a lint, add axe to historibook. Run over the already generated screens and fix the regressions.
Next is the next component. After a month, the system will begin to resist bad code itself, and AI, which reads its types and examples, will begin to generate exactly what you sew into the contract.
**Accessibility in the age of AI generation is not a separate discipline or item on the checklist before release. It is a property of the contract between the design system, types and tools. Make the contract strict and the assistant will work for you. Leave the holes and it will expand them faster than any manual command.