On 4 June 2026, a court in Caen, France, ordered Carrefour to make its website and app fully accessible to disabled customers within six months, with a penalty for every day it runs late. Two disability associations, apiDV and Droit Pluriel, brought the case under the French law that carries the European Accessibility Act.
Carrefour's defense was a number. It said the site already met 71% of the accessibility criteria. The judge said it had to be totally accessible (Deque, June 2026).
Seventy-one percent sounds like a pass. For a blind shopper who can't get through checkout, it's a locked door.
This part takes six popular myths about accessibility, puts each one against the evidence, and then builds the CI gate that stops you shipping your own 71%.
TL;DR
- Aim for WCAG 2.2 Level AA. Meeting 2.2 also meets 2.0 and 2.1, which most laws still name.
- Six boring error types made up 96% of what WebAIM found in 2026. Native HTML prevents most of them for free.
- Gate CI on axe-core, add role-based Playwright tests, and keep a human screen reader pass, because a clickable
divsails through axe.- The law is live: the EAA since 28 June 2025, the US ADA Title II rule from 26 April 2027.
- Streaming LLM answers should be announced in sentences, not tokens. The same markup serves screen readers and AI browser agents.
What: accessibility, defined precisely
Accessibility means people with disabilities can perceive, understand, operate and navigate your product, with or without assistive technology like screen readers, magnifiers or voice control. Don't confuse it with conformance, which is passing a standard's testable criteria. Conformance is the measurable proxy. Accessibility is the outcome, and you can have the first without the second.
The measurable form is the Web Content Accessibility Guidelines (WCAG). Version 2.2 became a W3C Recommendation in 2023 and the international standard ISO/IEC 40500:2025 in October 2025. Its criteria come in levels A, AA and AAA, and when a law says "accessible", it almost always means AA.
Scope: web content, and in most legal readings apps and documents too.
What 2.2 adds at A and AA:
- 2.4.11 Focus Not Obscured (AA): sticky headers can't completely hide the focused element.
- 2.5.7 Dragging Movements (AA): anything you drag also works with a single tap.
- 2.5.8 Target Size (AA): pointer targets of at least 24 by 24 CSS pixels, or enough spacing.
- 3.2.6 Consistent Help (A): help links stay in the same place.
- 3.3.7 Redundant Entry (A): don't make people retype what they gave you.
- 3.3.8 Accessible Authentication (AA): no memory test or puzzle to sign in. Paste, password managers and passkeys satisfy it.
A testable requirement reads: "Every route passes automated WCAG 2.2 AA checks on every pull request, sign-in and checkout complete with a keyboard and a screen reader, and no pointer target is under 24 by 24 CSS pixels."
Why: who you're leaving out, and who's watching
About 1.3 billion people, 16% of the world's population, live with a significant disability (WHO, 2023). Add the temporary and situational cases: a broken wrist, glare on a phone, a supervisor on a loud factory floor who can't hear an alert.
The web mostly fails them. WebAIM's 2026 scan of the top million home pages found detectable WCAG failures on 95.9% of them, up from 94.8% and reversing six years of slow improvement. The average page had 56.1 errors (WebAIM Million, 2026).
Then there's the law, which is where accessibility starts to look like Part 3 on Compliance: a regulation, a standard it points to, and evidence you produce on demand. Myth 5 has the dates.
Six myths, checked against the evidence
Myth 1: "Accessibility is for a small group of users"
One in six people isn't a niche, and it undercounts your users. Captions get used in noisy rooms. A visible focus ring helps anyone fighting a bad trackpad.
There's a newer group of users, too. Screen readers, voice control, your Playwright tests and AI browser agents all read the same thing: the accessibility tree the browser builds from your markup. OpenAI's developer FAQ says "making your website more accessible helps ChatGPT Agent in Atlas understand it better". Microsoft's Playwright MCP server drives a browser for an LLM from that tree, not from screenshots.
flowchart LR
M["Your HTML<br/>and ARIA"] --> T["Browser accessibility tree<br/>roles, names, states"]
T --> SR["Screen readers"]
T --> VC["Voice control"]
T --> PT["Playwright<br/>getByRole tests"]
T --> AG["AI browser agents"]
Every one of these reads the same tree, so one missing button name breaks all of them at once.
Myth 2: "It's expensive and slows us down"
Here are WebAIM's most common failures in 2026, by share of home pages: low contrast text (83.9%), missing alt text (53.1%), missing form labels (51%), empty links (46.3%), empty buttons (30.6%) and no page language (13.5%). Those six made up 96% of every error detected.
Each one is a line of code. A checked color token, an alt attribute, a <label for>, text in a link, a name on an icon button, lang="en". None of that slows a sprint, and native <button> and <a href> bring keyboard support and the right role for free.
The real cost lives in two places: retrofitting, which Carrefour now does on a judge's clock, and custom widgets like comboboxes and data grids. For those I'd use a tested library such as React Aria, which ships over 40 unstyled components tested with screen readers, rather than build my own. You still test what you build with it.
Myth 3: "A scanner, or an overlay, makes us compliant"
Automated tools are necessary and nowhere near enough. In 2017 the UK government built a page with 143 deliberate barriers and ran ten free checkers on it. Together they found 71%, and 42 barriers got past every tool (GOV.UK, 2017). Deque, which makes axe, puts its own coverage at 57% of issues by volume (2021), a generous way of counting that still leaves four in ten for people.
Here's what that looks like. A sign-in page with a <div onclick> reading "Continue" passed axe-core 4.14 with zero violations. A Playwright test looking for a button named "Continue" failed immediately.
flowchart LR
D["div onclick<br/>with text Continue"] --> AX["axe-core scan"]
AX -->|"no rule fails"| G["Green build"]
D --> SR["Screen reader<br/>and keyboard"]
SR -->|"read as plain text<br/>skipped by Tab"| X["User stuck<br/>at sign-in"]
D --> RT["getByRole button test"]
RT -->|"button not found"| R["Red build"]
The same clickable div passes the scanner and fails both the user and a role-based test.
Overlays, the one-script-tag fixes, fail the same way at a higher price. In April 2025 the US Federal Trade Commission finalized a $1 million order against accessiBe, saying its AI-powered widget did not make all customer sites WCAG-compliant, and barred it from claiming otherwise without evidence (FTC, 2025).
Myth 4: "More ARIA makes it more accessible"
In WebAIM's 2026 data, pages using ARIA averaged 59.1 errors against 42 without. WebAIM calls that a correlation, since ARIA-heavy pages are also more complex. It still matches the W3C's first rule of ARIA: if a native element already does what you need, use it.
ARIA changes what assistive technology is told. It doesn't add behavior. role="button" on a div won't make it focusable or make Enter work.
Agents add a twist. That OpenAI FAQ talks about "ARIA tags", and accessibility consultant Adrian Roselli replied that native HTML is the right advice and that ARIA labels will get keyword-stuffed like alt text was. Keep ARIA for what HTML can't say, like aria-expanded or aria-describedby wired up with React's useId. And never write an aria-label for a bot. WCAG 2.5.3 Label in Name expects the accessible name to contain the visible label, so voice control users can say what they see.
Myth 5: "We're not in the EU or government, so the law can't reach us"
The European Accessibility Act has applied since 28 June 2025 (EUR-Lex). It covers products and services for EU consumers, including e-commerce, banking, e-books, transport and telephony. Only microenterprises providing services (under 10 staff and €2 million turnover) are exempt, and each member state sets its own penalties. The standard behind it, EN 301 549, published version 4.1.1 with WCAG 2.2 AA in September 2026. At the time of writing it isn't yet cited in the EU's Official Journal, so version 3.2.1 is still the legal reference.
In the US, the ADA Title II rule requires WCAG 2.1 AA for state and local government web content and apps, including what contractors supply. An April 2026 rule moved the dates to 26 April 2027 for entities serving 50,000 people or more and 26 April 2028 for the rest (Federal Register, 2026). The Justice Department says it may revisit the substance, so check before you plan around it.
Enforcement is real but uneven. A month before Carrefour, a Lille court dismissed a similar case against Auchan's online arm on a revenue threshold, and that's under appeal. If you sell into Europe, or a city council buys your software, the law already reaches you.
When: where accessibility enters the lifecycle
- Design: check contrast and focus styles in your tokens, keep targets at 24 by 24 CSS pixels, and annotate headings and button names in mockups. Developers can't guess what an icon was meant to say.
-
Build: native elements first, a tested library for complex widgets,
eslint-plugin-jsx-a11yin the editor. - Test: axe-core plus role and keyboard tests on every pull request, then a screen reader pass (NVDA with Chrome, VoiceOver with Safari) on key journeys before each release.
- Operate: publish an accessibility statement with a working feedback channel, treat accessibility bugs as defects, and re-check production journeys on a schedule.
Where: AWS building blocks and the open-source tools around them
AWS doesn't sell an accessibility service. It gives you pieces that make particular criteria easier, and places to run checks.
- AWS Amplify UI: accessible React components, including the sign-in Authenticator. Useful on Amplify, unnecessary if you have a design system. Your theme can still break contrast.
- Amazon Cognito managed login: passkeys and one-time codes from the Essentials tier up, which gives you a sign-in that meets 3.3.8. Test your branding's contrast.
-
Amazon CloudWatch Synthetics: scheduled Playwright canaries (
syn-nodejs-playwright-8.0, Playwright 1.61.1) to replay journey tests against production. Its Playwright trails the current release. - Amazon Transcribe: live captions, with partial-result stabilization so words don't flicker. Recorded media still needs human-edited captions.
- Amazon Polly: read-aloud at $4 to $100 per million characters by engine. Word-highlighting speech marks don't work with generative voices.
The core tools are open source. axe-core powers most scanners, Lighthouse included, so Lighthouse isn't a second opinion. pa11y-ci checks a URL list with no test code. And a free screen reader like NVDA is the only tool that tells you what a user will actually hear.
Decide: which check for which problem
| Check | Catches | Misses | Cost |
|---|---|---|---|
| eslint-plugin-jsx-a11y | Click handlers on divs, missing alt, invalid ARIA |
Anything that depends on CSS or runtime state | Free, seconds |
| axe-core via Playwright | Contrast, names, labels, lang, invalid ARIA, target size with the 2.2 tag |
Fake controls, focus order, alt text quality | Free, seconds per route |
| Role and keyboard tests | Fake controls, broken Tab order, mouse-only journeys | Visual issues, meaning | Free, you write them |
| CloudWatch Synthetics canary | Regressions in live journeys | Whatever the script doesn't test | Billed per run |
| Manual screen reader pass | Announcements, reading order, confusing flows | Coverage at scale | People's time |
| Testing with disabled users | Whether it actually works for them | Nothing a cheaper check finds better | Recruiting and paying people |
When you're not sure which check a requirement needs, walk it down this tree.
flowchart TB
Q["A WCAG requirement to verify"] --> Q1{"Visible in the DOM alone?<br/>contrast, names, labels, lang"}
Q1 -->|"yes"| AX["axe-core in CI"]
Q1 -->|"no"| Q2{"Depends on behavior?<br/>focus, keyboard, announcements"}
Q2 -->|"yes"| PW["Playwright role<br/>and keyboard test"]
Q2 -->|"no"| Q3{"Depends on meaning?<br/>alt text, captions, plain language"}
Q3 -->|"yes"| HR["Human review<br/>AI may draft"]
Q3 -->|"no"| AU
AX --> AU["Before any conformance claim<br/>manual audit and user testing"]
PW --> AU
HR --> AU
Pick the cheapest check that can see the problem, and never let a passing tool stand in for the audit.
Accessibility also pulls with and against the rest of this series.
| NFR | Pulls the same way | Pulls against it |
|---|---|---|
| Security | Passkeys and password-manager paste meet 3.3.8 and cut phishing | CAPTCHAs, and timeouts without the warning and 20-second extension WCAG 2.2.1 asks for |
| Compliance | The same evidence trail as Part 3 | Laws name different versions: 2.1 for ADA Title II, 2.2 in EN 301 549 v4.1.1 |
| Performance | Native HTML ships less JavaScript than custom widgets or overlays | Throttled announcements arrive after the text does |
| Availability | Announced error messages make degraded modes usable | An accessible fallback is one more path to test |
| Scalability | Text and voice share one backend | Nova 2 Sonic caps at 20 concurrent sessions per account per Region |
| Maintainability | Fewer custom widgets, less keyboard code | Hand-built ARIA widgets need their own tests forever |
| Cost | A lint error takes minutes to fix | Manual testing and user research are real budget lines |
| Sustainability | Lean, semantic pages | Speech output adds compute per interaction |
Hands-on: a WCAG 2.2 AA gate in CI
Take Harbor Works, the fictional plant in Luso, the open-source AI watchman for production lines I'm building alongside this series. Its web app has a status page, a sign-in page and an alerts page, and a shift supervisor has to be able to use each one with a keyboard and a screen reader.
flowchart LR
PR["Pull request"] --> L["ESLint jsx-a11y<br/>markup rules"]
L --> B["Build the app"]
B --> S["Playwright serves<br/>the production build"]
S --> A["axe-core scan<br/>every route, 2.2 AA tags"]
A --> J["Role and keyboard<br/>journey test"]
J -->|"all pass"| M["Merge"]
L -->|"fail"| F["Build fails with<br/>rule ids and selectors"]
A -->|"fail"| F
J -->|"fail"| F
M --> H["Manual screen reader<br/>pass before release"]
Each stage catches what the one before it can't, and a person still checks key journeys before release.
For the lint step, add jsxA11y.flatConfigs.recommended from eslint-plugin-jsx-a11y 6.10.2 to your eslint.config.js. The Playwright config below assumes a Vite app with npm run preview. Set BASE_URL to test a preview deployment instead.
Checked against Playwright accessibility testing and Playwright reporters on Oct 29, 2026. Ran locally with Node 22, @playwright/test 1.64.1, @axe-core/playwright 4.14.0 and Chromium against static test pages. Not deployed — test in a sandbox account first.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
forbidOnly: !!process.env.CI,
reporter: [
['list'],
['html', { open: 'never' }], // full report as a CI artifact
['junit', { outputFile: 'reports/a11y-junit.xml' }], // CodeBuild test reports read this
],
use: { baseURL: process.env.BASE_URL ?? 'http://localhost:4173' },
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
// Serve the production build unless BASE_URL points at a preview deployment
webServer: process.env.BASE_URL ? undefined : {
command: process.env.A11Y_SERVE ?? 'npm run preview -- --port 4173 --strictPort',
url: 'http://localhost:4173',
reuseExistingServer: !process.env.CI,
},
});
The test scans every route with axe, then walks sign-in the way a screen reader user would: by label, role and keyboard.
Checked against Playwright accessibility testing and axe-core API tags on Oct 29, 2026. Ran locally as above. Not deployed — test in a sandbox account first.
// tests/a11y.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
// axe-core tags for WCAG 2.0, 2.1 and 2.2 at levels A and AA.
// Drop 'wcag22aa' and the WCAG 2.2 target-size rule never runs.
const WCAG_22_AA = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'];
const ROUTES = (process.env.A11Y_ROUTES ?? '/,/sign-in/,/alerts/').split(',');
for (const route of ROUTES) {
test(`${route} has no detectable WCAG 2.2 AA violations`, async ({ page }, testInfo) => {
await page.goto(route);
await page.getByRole('main').waitFor(); // also fails pages with no main landmark
const results = await new AxeBuilder({ page })
.withTags(WCAG_22_AA)
.exclude('[data-a11y-ignore]') // third-party embeds you track as known issues
.analyze();
// Full JSON in the report: selectors, failure summaries and help URLs for whoever fixes it
await testInfo.attach('axe-violations.json', {
body: JSON.stringify(results.violations, null, 2),
contentType: 'application/json',
});
const summary = results.violations.map(
(v) => `${v.id} [${v.impact}] x${v.nodes.length}: ${v.help}`,
);
expect(summary).toEqual([]);
});
}
// axe can't tell a clickable <div> from plain text. Role and label queries can:
// if this fails, screen readers, voice control and browser agents can't find the control either.
test('sign-in works by label, role and keyboard alone', async ({ page }) => {
await page.goto(process.env.A11Y_SIGN_IN ?? '/sign-in/');
await page.getByLabel('Email').fill('supervisor@harbor.example');
await page.keyboard.press('Tab');
const submit = page.getByRole('button', { name: 'Continue' });
await expect(submit).toBeFocused(); // reachable, in order, by keyboard
await page.keyboard.press('Enter');
await expect(page.getByRole('heading', { level: 1, name: 'Alerts' })).toBeVisible();
});
Running it showed three things. A broken test page failed with four named rules (html-has-lang, image-alt, link-name, color-contrast). The clickable-div page passed the scan and failed the journey test. And axe-core 4.14 only checks target size when you ask for the wcag22aa tag. A default run let 10-pixel buttons through. Keep exclude rare, since it switches off every rule inside that element.
On GitHub Actions:
Checked against Playwright CI docs on Oct 29, 2026. YAML validated, not run on GitHub. Not deployed — test in a sandbox account first.
# .github/workflows/a11y.yml
name: a11y
on:
pull_request:
push:
branches: [main]
jobs:
a11y:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx eslint . # jsx-a11y: div buttons, missing alt, before a browser starts
- run: npm run build
- run: npx playwright install --with-deps chromium
- run: npx playwright test # GitHub sets CI=true, so test.only fails the build
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }} # upload the report when tests fail, too
with:
name: a11y-report
path: playwright-report/
retention-days: 30
If your pipeline runs in AWS, here's the same gate for CodeBuild. Playwright supports only Debian and Ubuntu, so use the Ubuntu standard image. CodeBuild doesn't document setting CI, so the buildspec sets it. The JUnit file feeds CodeBuild test reports, so each failing page shows up as a failed test case.
Checked against the CodeBuild buildspec reference and CodeBuild available runtimes on Oct 29, 2026. YAML validated, not run in CodeBuild. Not deployed — test in a sandbox account first.
# buildspec.yml
# CodeBuild image: aws/codebuild/standard:7.0 (Ubuntu 22.04). Playwright supports Debian and Ubuntu only.
# Pinned alternative image: mcr.microsoft.com/playwright:v1.64.1-noble
version: 0.2
env:
variables:
CI: "true" # the Playwright config reads it
phases:
install:
runtime-versions:
nodejs: 22
commands:
- npm ci
- npx playwright install --with-deps chromium
pre_build:
commands:
- npx eslint .
- npm run build
build:
commands:
- npx playwright test
reports:
a11y-tests: # becomes report group <project-name>-a11y-tests
files:
- a11y-junit.xml
base-directory: reports
file-format: JUNITXML
artifacts:
name: a11y-report
base-directory: playwright-report
files:
- '**/*'
That's the automated slice, about half the problem by the most generous count. The manual pass in the diagram isn't optional.
Accessibility in the age of AI
Myth 6: "AI makes accessibility free"
The Justice Department answered this one. Delaying the ADA dates, it wrote that "advanced technology, such as generative AI, does not yet reliably automate the remediation of inaccessible content at scale". That's a regulator that wanted the shortcut to exist.
Research agrees. A 2025 study comparing web code from GPT-4o and Qwen2.5-Coder with human code found the models often better on basics like contrast and alt text, and weak on ARIA. Better prompts helped a little. Feeding test results back to the model helped a lot. WebAIM even suggests AI-assisted "vibe coding" may be part of why errors rose in 2026, though it didn't measure that.
Alt text shows why "free" is the wrong word. A model sees pixels, but alt text depends on purpose: the same photo needs empty alt as decoration and a description when it informs. Screen reader users can already get generic descriptions themselves (60.1% in WebAIM's 2026 screen reader survey use AI to describe images). What only you can add is why the image is there. Let AI draft alt text and captions, and have a person approve them.
AI as a workload: streaming answers and live regions
Streaming makes chat feel fast and can make it unbearable with a screen reader. If the answer container is an aria-live region, every token is a change, and users hear fragments, restarts, or a queue that runs long after the text has finished.
The fix is to separate what's shown from what's announced. A Harbor Works supervisor asks why line 3 stopped. The visible answer updates per token. A separate role="status" region, polite by default, only gets whole sentences, at most one every 1.5 seconds, then "Answer complete."
sequenceDiagram
participant U as Supervisor
participant UI as Chat UI
participant B as Bedrock
participant L as Live region
participant SR as Screen reader
U->>UI: Why did line 3 stop
UI->>B: ConverseStream request
loop every token
B-->>UI: contentBlockDelta text
UI->>UI: Append to visible answer and sentence buffer
opt sentence ended and 1.5 s since last announcement
UI->>L: Replace text with the whole sentence
L->>SR: Polite announcement
end
end
B-->>UI: messageStop
UI->>L: Remaining text plus Answer complete
L->>SR: Final polite announcement
Tokens reach the screen as fast as they arrive, but the screen reader only hears whole sentences.
Your backend calls Bedrock's ConverseStream and forwards each contentBlockDelta text, and getTokens reads that stream. Add a standard visually-hidden CSS class.
Checked against MDN ARIA live regions and the Bedrock ConverseStream reference on Oct 29, 2026. Type-checked with React 19 and run in Chromium against a simulated stream, not with a real screen reader or Bedrock. Not deployed — test in a sandbox account first.
import { useEffect, useRef, useState } from 'react';
type Props = {
// A factory, not a stream: StrictMode runs effects twice, so each run needs its own request
getTokens: (signal: AbortSignal) => AsyncIterable<string>;
announce?: 'sentences' | 'complete'; // let users pick, and save it in their settings
};
const MIN_GAP_MS = 1500; // never speak more often than this
const SENTENCE_END = /[.!?]\s*$|\n$/;
export function StreamingAnswer({ getTokens, announce = 'sentences' }: Props) {
const [shown, setShown] = useState(''); // what sighted users see: every token
const [spoken, setSpoken] = useState(''); // what the live region says: whole sentences
const pending = useRef('');
const lastSpoke = useRef(0);
useEffect(() => {
const ctrl = new AbortController();
setShown(''); setSpoken(''); pending.current = '';
(async () => {
try {
for await (const t of getTokens(ctrl.signal)) {
if (ctrl.signal.aborted) return;
setShown((s) => s + t);
pending.current += t;
const now = Date.now();
if (announce === 'sentences' && SENTENCE_END.test(pending.current)
&& now - lastSpoke.current >= MIN_GAP_MS) {
setSpoken(pending.current.trim());
pending.current = '';
lastSpoke.current = now;
}
}
if (ctrl.signal.aborted) return;
// Flush the tail, then say it's finished so nobody sits waiting for more
setSpoken(announce === 'complete'
? 'Answer ready.'
: `${pending.current.trim()} Answer complete.`);
pending.current = '';
} catch {
if (!ctrl.signal.aborted) setSpoken('The answer failed to load. Try again.');
}
})();
return () => ctrl.abort();
}, [getTokens, announce]);
return (
<section aria-labelledby="answer-heading">
<h2 id="answer-heading">Answer</h2>
<div>{shown}</div>{/* not live: reading it is the user's choice */}
{/* In the DOM from first render so screen readers register it before it changes */}
<div role="status" className="visually-hidden">{spoken}</div>
</section>
);
}
In a simulated run, a 48-token answer produced two announcements instead of 48 and passed the same axe scan. Some users would rather read the finished answer at their own pace, so make announce="complete" a user setting.
Voice interfaces
Voice helps users with motor or vision impairments, and anyone whose hands are busy. On AWS you can chain Amazon Transcribe, a text model and Amazon Polly, keeping a transcript at every step. Or use Amazon Nova 2 Sonic (amazon.nova-2-sonic-v1:0, generally available since December 2025), a speech-to-speech model on Bedrock's InvokeModelWithBidirectionalStream API that accepts speech and text in one session, in seven languages.
Know its limits. It runs In-Region only (us-east-1, us-west-2, eu-north-1, ap-northeast-1), connections last eight minutes, and the quota of 20 concurrent sessions per account per Region isn't adjustable. Its model card lists Bedrock Guardrails as unsupported, so content controls live in your prompts and tools. And voice must never be the only way in. A Deaf user needs the same task in text.
AI agents as users
Browser agents read the accessibility tree, so Myths 1 and 4 apply to them as well. A form a screen reader can finish is a form an agent can finish, which makes your getByRole tests a fair proxy for agent-readiness. If an agent can't find a button, the fix is the same native <button> a person needs.
AI as an engineering aid
Assistants happily generate a div with an onClick, and that 2025 study suggests prompting alone won't fix it. Feedback does. So:
- Put "native elements, a label on every input, no click handlers on non-interactive elements" in your assistant's project instructions.
- Feed it the axe JSON and the failing role test, not "make it accessible".
- Have a person review every ARIA attribute it adds, since that's where models were weakest.
What's still immature is AI doing the manual pass. No tool today replaces a person with a screen reader on your checkout.
Who: owners and the shared responsibility split
| Activity | Product owner | Designer | Developers | QA / platform |
|---|---|---|---|---|
| Set the target and legal scope, with legal advice | A | C | C | C |
| Contrast, focus and target-size tokens | I | R/A | C | I |
| Semantic components, labels, live regions | I | C | R/A | C |
| CI gate and production canaries | I | I | R | A |
| Screen reader pass before release | C | C | R | A |
| Approve AI-drafted alt text and captions | A | R | R | I |
R = responsible, A = accountable, C = consulted, I = informed.
Shared responsibility barely changes with compute type. On EC2, containers or Lambda, AWS owns its console's accessibility and nothing in your app. With Amplify UI, AWS owns component semantics, and you own labels, composition and theme. With Bedrock, the provider owns what the model generates, and you own how it's presented, announced, captioned and reviewed.
Monday checklist
- [ ] Write down your target (WCAG 2.2 AA) and which laws reach you: the EAA, ADA Title II through a public-sector customer, or both.
- [ ] Add
eslint-plugin-jsx-a11ywith the recommended config and fix what it finds in shared components. - [ ] Add the axe-core Playwright test with the
wcag22aatag to CI for your three busiest routes. - [ ] Write one role-and-keyboard journey test for sign-in and one for your main task.
- [ ] Check design tokens for 4.5:1 text contrast and a visible focus style.
- [ ] Spend 15 minutes on checkout or sign-in with NVDA or VoiceOver, and file what you hear.
- [ ] If you stream LLM output, announce sentences from a separate
role="status"region. - [ ] Put a human approval step on AI-generated alt text and captions.
Conclusion
Every myth here grows from one belief: that accessibility is a feature you add, buy or generate. The evidence says it's mostly a property of ordinary code. Six boring error types cover nearly every failure. A real <button> beats a clever widget. Scanners find part of the problem, overlays and AI drafts don't close the rest, and courts and regulators have stopped accepting "mostly".
Carrefour's 71% measured the wrong thing. Your users don't experience a percentage. They finish the task or they don't.
So build the gate, write the role tests, and put a person with a screen reader on the journeys that matter.
If a screen reader can't find it, neither can your users, your tests or your agents.
Series: Non-functional Application Requirements
- Non-functional Application Requirements: An Introduction
- Non-functional Application Requirements: Security
- Non-functional Application Requirements: Compliance
- Accessibility
- Performance (coming soon)
- Availability (coming soon)
- Scalability (coming soon)
- Maintainability (coming soon)
- Cost (coming soon)
- Sustainability (coming soon)
Cover image: AWS re:Invent 2023 – Keynote with Dr. Werner Vogels
Next up, Performance: p95 budgets, caching layers and time-to-first-token, and why a page that's merely less slow still sells more.
Sources
- France's major court decision supporting digital accessibility under the EAA — Deque, June 2026
- Accessibilité web : Carrefour condamné par la justice — Handicap.fr, June 2026
- EAA: Auchan court ruling — Silktide, 2026
- Disability fact sheet — World Health Organization, March 2023
- The WebAIM Million: 2026 report on the accessibility of the top 1,000,000 home pages — WebAIM, 2026
- Screen Reader User Survey #11 results — WebAIM, 2026
- Web Content Accessibility Guidelines (WCAG) 2.2 — W3C Recommendation, December 2024
- What's new in WCAG 2.2 — W3C WAI
- WCAG 2.2 approved as an ISO standard — W3C WAI, October 2025
- Understanding SC 2.5.8 Target Size (Minimum) — W3C WAI
- Understanding SC 3.3.8 Accessible Authentication (Minimum) — W3C WAI
- Understanding SC 2.2.1 Timing Adjustable — W3C WAI
- Using ARIA: the first rule of ARIA use — W3C
- An alt decision tree — W3C WAI
- Directive (EU) 2019/882, the European Accessibility Act — EUR-Lex
- European Accessibility Act ICT standards update — Davis Wright Tremaine, September 2026
- Extension of compliance dates for the ADA Title II web and mobile accessibility rule — Federal Register, April 2026
- Title II web rule — ADA.gov
- FTC approves final order requiring accessiBe to pay $1 million — FTC, April 2025
- What we found when we tested tools on the world's least-accessible webpage — GOV.UK Accessibility blog, 2017
- Automated testing study identifies 57 percent of digital accessibility issues — Deque, 2021
- Publishers and developers FAQ — OpenAI Help Center
- OpenAI, ARIA, and SEO: Making the Web Worse — Adrian Roselli, October 2025
- Playwright MCP — Microsoft, GitHub
- Human or LLM? A Comparative Study on Accessible Code Generation Capability — Suh et al., arXiv, 2025
- Accessibility testing — Playwright docs
- Reporters — Playwright docs
- Setting up CI — Playwright docs
- Installation and system requirements — Playwright docs
- Docker — Playwright docs
- axe-core API documentation — Deque, GitHub
- axe-core rule descriptions — Deque, GitHub
- eslint-plugin-jsx-a11y — GitHub
- pa11y — GitHub
- React Aria — Adobe
- useId — React docs
- ARIA live regions — MDN
- Lighthouse accessibility scoring — Chrome for Developers
- Buildspec reference — AWS CodeBuild docs
- Available runtimes — AWS CodeBuild docs
- Accessibility — Amplify UI docs
- Improve your app authentication workflow with new Amazon Cognito features — AWS News Blog, November 2024
- Playwright runtime versions for CloudWatch Synthetics — Amazon CloudWatch docs
- Streaming transcription — Amazon Transcribe docs
- Partial-result stabilization — Amazon Transcribe docs
- Speech marks — Amazon Polly docs
- Generative voices — Amazon Polly docs
- Amazon Polly pricing — AWS
- Announcing Amazon Nova 2 Sonic for real-time conversational AI — AWS, December 2025
- Nova 2 Sonic model card — Amazon Bedrock docs
- Speech-to-speech (Amazon Nova 2 Sonic) — Amazon Nova docs
- Amazon Nova 2 release notes — Amazon Nova docs
- InvokeModelWithBidirectionalStream — Amazon Bedrock API reference
- ConverseStream — Amazon Bedrock API reference

Top comments (0)