Docs › European Accessibility Act
EAA Form Lint: WCAG 2.2 AA for HTML
Names every WCAG 2.2 AA form failure on its line, with the criterion and the fix
What it checks (18 rules: 14 error, 4 warn)
| Rule | Level | What it flags | WCAG 2.2 | Fix |
|---|---|---|---|---|
auth_paste_blocked | error | Sign-in field blocks paste or drop | 3.3.8 (AA) | delete the onpaste/oncopy/ondrop handler so a password manager can fill the field |
auth_no_autocomplete | error | Password field is hidden from password managers | 3.3.8 (AA) | set autocomplete="current-password" (or "new-password" on sign-up) |
input_purpose | error | Identity field has no autocomplete token | 1.3.5 (AA) | add the WCAG token, e.g. autocomplete="email" / "tel" / "postal-code" |
no_label | error | Form control has no accessible name | 4.1.2 (A) | add <label for="id"> or aria-label on the control |
placeholder_as_label | error | Placeholder is doing the job of a label | 3.3.2 (A) | keep the placeholder as an example and add a visible <label for="id"> |
target_size | error | Interactive target is smaller than 24 by 24 CSS pixels | 2.5.8 (AA) | raise the box to at least 24px, or leave 24px of clear space around it |
redundant_entry | warn | The same information is asked for twice | 3.3.7 (A) | drop the confirm field, or let the browser auto-fill it from the first one |
focus_removed | error | Focus outline removed with no visible replacement | 2.4.7 (AA) | keep the outline, or style :focus-visible with a 2px visible indicator |
zoom_blocked | error | Viewport blocks zoom | 1.4.4 (AA) | remove user-scalable=no and maximum-scale from the viewport meta |
lang_missing | error | Page language is not declared | 3.1.1 (A) | add lang="en" (or the page language) to the <html> element |
img_alt | error | Image has no alt attribute | 1.1.1 (A) | add alt="...", or alt="" when the image is decorative |
icon_no_name | error | Icon-only control has no accessible name | 4.1.2 (A) | add aria-label="..." describing the action |
positive_tabindex | warn | Positive tabindex overrides the reading order | 2.4.3 (A) | use tabindex="0" and let the DOM order carry the focus order |
click_no_key | error | Click handler that the keyboard cannot reach | 2.1.1 (A) | use a <button>, or add role="button" plus tabindex="0" and a key handler |
aria_hidden_focusable | error | Focusable element is hidden from assistive technology | 4.1.2 (A) | remove aria-hidden, or take the element out of the tab order |
meta_refresh | error | Page refreshes or redirects on a timer | 2.2.1 (A) | remove the meta refresh, or let the user turn it off or extend it |
dragging_only | warn | Dragging with no single-pointer alternative | 2.5.7 (AA) | add a move up/down button, and mark the element data-dnd-alternative |
error_no_desc | warn | Invalid field does not point at its error message | 3.3.1 (A) | add aria-describedby="<id of the error text>" next to aria-invalid |
Run it
ext install ReadyStack.eaa-form-lint-wcag22 # VS Code ยท Cursor/VSCodium via Open VSX npx -y @readystack/eaa-form-lint-wcag22 signup.html # CLI, free, every rule npx -y @readystack/eaa-form-lint-wcag22 --dir . --ci # whole repository, exit 1 on errors (licence) docker run --rm -v "$PWD:/w" getreadystack/eaa-form-lint-wcag22 /w # Docker
MCP (Claude Code, Cursor, Windsurf): check_text and check_file are free, check_dir needs a key.
{ "mcpServers": { "eaa-form-lint-wcag22": { "command": "npx", "args": ["-y", "@readystack/eaa-form-lint-wcag22", "--mcp"] } } }Sample report
A deliberately wrong sample file, then the real output of npx @readystack/eaa-form-lint-wcag22 signup.html on it. Nothing here is mocked.
Sample input (signup.html)
<form action="/signup" method="post"> <input type="text" placeholder="Full name"> <input type="email" placeholder="Email address"> <input type="password" placeholder="Password" autocomplete="off" onpaste="return false"> <input type="checkbox" id="tos"> I agree <button type="submit">Create account</button> </form>
Output
/tmp/s152_flag/samples/signup.html
2 error WCAG 2.2 3.3.2 (A) Placeholder is doing the job of a label: Full name - fix: keep the placeholder as an example and add a visible <label for="id">
3 error WCAG 2.2 3.3.2 (A) Placeholder is doing the job of a label: Email address - fix: keep the placeholder as an example and add a visible <label for="id">
3 error WCAG 2.2 1.3.5 (AA) Identity field has no autocomplete token: email - fix: add the WCAG token, e.g. autocomplete="email" / "tel" / "postal-code"
4 error WCAG 2.2 3.3.8 (AA) Sign-in field blocks paste or drop - fix: delete the onpaste/oncopy/ondrop handler so a password manager can fill the field
4 error WCAG 2.2 3.3.2 (A) Placeholder is doing the job of a label: Password - fix: keep the placeholder as an example and add a visible <label for="id">
4 error WCAG 2.2 3.3.8 (AA) Password field is hidden from password managers: autocomplete="off" - fix: set autocomplete="current-password" (or "new-password" on sign-up)
5 error WCAG 2.2 4.1.2 (A) Form control has no accessible name: <input id="tos"> - fix: add <label for="id"> or aria-label on the controlFree and licence
| Free | Lints the HTML, Vue or Svelte file you have open with all 18 rules and names every WCAG 2.2 AA form failure on its line, with the success-criterion number and the one-line fix. |
|---|---|
| With a key ($29 once) | Scans every template in the workspace in one pass and writes a dated conformance evidence file you keep - one row per finding, per file, mapped to its WCAG 2.2 criterion. |
The full run is free for the first 7 days. 7-day refund on every key. Worth it because: A WCAG audit from an accessibility vendor starts around $2,500 for one signup flow
Get the key, $29 once Team key, every linter, $149
What comes next
The accessibility statement and EN 301 549 conformance evidence a client's procurement team asks for before signing
Questions
What does EAA Form Lint actually check?
It reads an HTML, Vue or Svelte template as text and applies 18 rules drawn from WCAG 2.2 Level AA. Each finding names the line, the success criterion and the fix: a password field hidden from password managers, a placeholder standing in for a label, a 16px tap target, a click handler no keyboard can reach.
Who is this for?
Frontend and full-stack developers shipping consumer-facing web services into the EU: e-commerce, banking, ticketing, e-books. Also the freelancer who inherits a generated codebase and has to answer a client asking whether the signup flow meets the European Accessibility Act, which has applied to new services since 28 June 2025.
Why is a free browser audit not enough?
Browser audit panels score a rendered page you already deployed, and most still test WCAG 2.1. Six of the 28 findings in the sample form sit under criteria added in WCAG 2.2 - dragging, target size, redundant entry, accessible authentication. This runs on the template in your editor, before the page exists.
What is free and what needs a key?
Checking the file you have open is free and complete: all 18 rules, every line, the criterion and the fix, no key and no limit. The licence key adds a different job - scanning every template in the workspace in one pass and writing a dated evidence file you keep and hand to a client.
What would this cost otherwise?
A WCAG audit from an accessibility vendor starts around $2,500 for a single signup flow, and its answer is true for the day it was run. A specialist re-reviewing each pull request costs more. This extension is $29 once, and it answers again every time you save a template.
A linter tells you what the rule says and where your file disagrees. It is not legal advice; the rule set carries its own date and is updated when the regulation moves.
Install free
Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.
VS Code MarketplaceOpen VSX (Cursor, VSCodium)npmMCP server (npx)Docker Hubext install ReadyStack.eaa-form-lint-wcag22 npx @readystack/eaa-form-lint-wcag22 <file> npx @readystack/eaa-form-lint-wcag22 --mcp
Team? One key for every ReadyStack linter, 5 seats, $149 once