Spaces:
Running
Running
Download index.html from getreadystack/en301549-lint: direct link, hf CLI and curl.
- Browser
- Download file 43.1 kB
-
https://huggingface.co/spaces/getreadystack/en301549-lint/resolve/main/index.html
- Command line
-
hf download hf://spaces/getreadystack/en301549-lint/index.html
-
curl -L -o index.html https://huggingface.co/spaces/getreadystack/en301549-lint/resolve/main/index.html
43.1 kB
| <html lang="en"><meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>EN 301 549 Lint — European Accessibility Act rules, with the clause number, inside your editor</title> | |
| <meta name="description" content="Flags the EN 301 549 clause each line breaks, and separates what is in force today from what arrives with V4.1.1."> | |
| <style> | |
| :root{color-scheme:light dark} | |
| body{margin:0;font:16px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif; | |
| background:#fbfbfa;color:#16161d} | |
| @media(prefers-color-scheme:dark){body{background:#14141a;color:#e8e8ea}} | |
| .wrap{max-width:880px;margin:0 auto;padding:28px 20px 64px} | |
| h1{font-size:1.7rem;line-height:1.25;margin:.2em 0 .3em} | |
| p.sub{margin:0 0 1.4em;opacity:.8} | |
| textarea{width:100%;min-height:230px;font:13px/1.5 ui-monospace,Menlo,Consolas,monospace; | |
| padding:12px;border:1px solid #c9c9d1;border-radius:10px;background:transparent;color:inherit; | |
| box-sizing:border-box} | |
| button{font:600 15px/1 inherit;padding:12px 22px;border:0;border-radius:10px; | |
| background:#1d4ed8;color:#fff;cursor:pointer;margin:12px 0} | |
| button:hover{background:#1a43bd} | |
| table{border-collapse:collapse;width:100%;margin-top:14px;font-size:14px} | |
| th,td{border-bottom:1px solid #d8d8e0;padding:8px 6px;text-align:left;vertical-align:top} | |
| th{font-weight:600;opacity:.75} | |
| td.n{width:64px;font:13px ui-monospace,monospace;opacity:.7} | |
| .ok{padding:14px;border-radius:10px;background:#e8f5ec;color:#14532d;margin-top:14px} | |
| @media(prefers-color-scheme:dark){.ok{background:#15321f;color:#bbf7d0}} | |
| .next{margin-top:30px;padding:18px;border:1px solid #d8d8e0;border-radius:12px} | |
| .next h2{font-size:1.05rem;margin:0 0 .5em} | |
| .cta{margin-top:18px;padding:16px 18px;border:1px solid #1d4ed8;border-radius:12px} | |
| .cta p{margin:0 0 .6em} | |
| .cta a.buy{display:inline-block;font:600 15px/1 inherit;padding:12px 22px;border-radius:10px;background:#1d4ed8;color:#fff;text-decoration:none} | |
| .cta p.terms{margin:.8em 0 0;font-size:13px;opacity:.75} | |
| .next ul{margin:.3em 0 1em 1.1em;padding:0} | |
| </style> | |
| <div class="wrap"> | |
| <h1>EN 301 549 Lint — European Accessibility Act rules, with the clause number, inside your editor</h1> | |
| <p class="sub">Flags the EN 301 549 clause each line breaks, and separates what is in force today from what arrives with V4.1.1.</p> | |
| <textarea id="src" placeholder="Paste the HTML, JSX, Vue, Svelte, Blade or Twig template you are looking at."></textarea> | |
| <button id="go">Check this file</button> | |
| <div id="out"></div> | |
| <div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&","<":"<",">":">",'"':"""}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div><div class="cta" id="cta" hidden><p>Whole-repository scan, the dated evidence file, and CI output.</p> | |
| <a href="https://getreadystack.com" class="buy">Get the full version — $29</a> | |
| <p class="terms">$29 once · one licence key per person or team seat · A manual WCAG audit is quoted at $100–$250 per page, and $1,250–$2,750 for a small site (Accessible.org, 2026).</p></div> | |
| <div class="next"><h2>In the full version</h2><ul><li>Scan every template in the repository in one pass</li><li>Export the dated evidence file (CSV, JSON or HTML)</li><li>Machine-readable output so CI can fail the build</li><li><a href="https://getreadystack.com">Get the full version</a></li></ul></div> | |
| </div> | |
| <script> | |
| var RULES=[{"pattern": "<img\\b(?![^>]*\\s(?:alt(?=[\\s=/>])|aria-label\\s*=|aria-labelledby\\s*=|role\\s*=\\s*[\"\\']?(?:presentation|none)\\b|aria-hidden\\s*=\\s*[\"\\']?true\\b))[^>]*>", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.1.1.1 (in force) — <img> has no alt attribute.", "fix": "<img src=\"…\" alt=\"What the image says\"> — or alt=\"\" if it is decorative."}, {"pattern": "<input(?=[^>]*type\\s*=\\s*[\"\\']image[\"\\'])(?![^>]*\\salt\\s*=)[^>]*>", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.1.1.1 (in force) — <input type=\"image\"> has no alt attribute.", "fix": "<input type=\"image\" src=\"…\" alt=\"Search\">"}, {"pattern": "<area(?![^>]*\\salt\\s*=)[^>]*>", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.1.1.1 (in force) — <area> in an image map has no alt attribute.", "fix": "<area shape=\"rect\" coords=\"…\" href=\"…\" alt=\"Where this region goes\">"}, {"pattern": "<(video|audio)\\b(?![^>]*\\bcontrols\\b)[^>]*>", "flags": "i", "sev": "warn", "message": "EN 301 549 V3.2.1 §9.1.2.2 (in force) — media element with no controls; captions/controls cannot be reached.", "fix": "<video controls><track kind=\"captions\" srclang=\"en\" src=\"captions.vtt\" default></video>"}, {"pattern": "<th(?![^>]*\\sscope\\s*=)[^>]*>", "flags": "i", "sev": "warn", "message": "EN 301 549 V3.2.1 §9.1.3.1 (in force) — <th> has no scope, so the header is not tied to its cells.", "fix": "<th scope=\"col\"> — or scope=\"row\" for a row header."}, {"pattern": "<label(?![^>]*\\sfor\\s*=)[^>]*>", "flags": "i", "sev": "warn", "message": "EN 301 549 V3.2.1 §9.1.3.1 (in force) — <label> has no for=, so it is not programmatically tied to a field.", "fix": "<label for=\"email\">Email</label><input id=\"email\">"}, {"pattern": "<input(?=[^>]*type\\s*=\\s*[\"\\'](email|tel)[\"\\'])(?![^>]*autocomplete)[^>]*>", "flags": "i", "sev": "warn", "message": "EN 301 549 V3.2.1 §9.1.3.5 (in force) — email/tel field has no autocomplete token.", "fix": "<input type=\"email\" autocomplete=\"email\"> · <input type=\"tel\" autocomplete=\"tel\">"}, {"pattern": "autocomplete\\s*=\\s*[\"\\']off[\"\\']", "flags": "i", "sev": "warn", "message": "EN 301 549 V3.2.1 §9.1.3.5 (in force) — autocomplete=\"off\" blocks the identified input purpose on name/address/payment fields.", "fix": "Use the token instead: autocomplete=\"given-name\" | \"postal-code\" | \"cc-number\"."}, {"pattern": "color\\s*:\\s*#(777|888|999|aaa|bbb|ccc)\\b", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.1.4.3 (in force) — grey text on white: #777 = 4.48:1, #888 = 3.54:1, #999 = 2.85:1. AA needs 4.5:1.", "fix": "#767676 is the lightest grey that reaches 4.5:1 on #fff. #666 = 5.74:1."}, {"pattern": "\\btext-(gray|grey|slate|zinc|neutral|stone)-(300|400)\\b", "flags": "", "sev": "error", "message": "EN 301 549 V3.2.1 §9.1.4.3 (in force) — Tailwind text-*-400 on white is about 2.8:1 and text-*-300 is lower. AA needs 4.5:1.", "fix": "text-gray-600 (#4b5563) = 7.5:1 on white. text-gray-500 = 4.8:1 for normal text only."}, {"pattern": "(user-scalable\\s*=\\s*[\"\\']?\\s*no|maximum-scale\\s*=\\s*[\"\\']?\\s*1(\\.0)?\\b)", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.1.4.4 (in force) — the viewport meta blocks zoom; text cannot be resized to 200%.", "fix": "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"}, {"pattern": "<(div|span|li|td|p)\\b[^>]*\\bonclick\\s*=", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.2.1.1 (in force) — a click handler on a non-interactive element: keyboard users cannot reach it.", "fix": "<button type=\"button\" onclick=\"…\"> — a real button is focusable and fires on Enter/Space."}, {"pattern": "(?:^|<(?!(?:button|input|select|textarea|summary)\\b|(?:a|area)\\b[^>]*\\shref\\s*=)[a-z][a-z0-9]*\\b)(?![^<>]*\\btabindex\\s*=)[^<>]*?\\brole\\s*=\\s*[\"\\']button[\"\\'](?![^>]*tabindex)", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.2.1.1 (in force) — role=\"button\" without tabindex is never reached by Tab.", "fix": "Add tabindex=\"0\" and a keydown handler for Enter and Space — or use <button>."}, {"pattern": "tabindex\\s*=\\s*[\"\\']?[1-9]", "flags": "i", "sev": "warn", "message": "EN 301 549 V3.2.1 §9.2.4.3 (in force) — a positive tabindex overrides DOM order and breaks focus order.", "fix": "tabindex=\"0\" to make it focusable, tabindex=\"-1\" for script focus only."}, {"pattern": "<(marquee|blink)\\b", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.2.2.2 (in force) — moving content that cannot be paused, stopped or hidden.", "fix": "Replace with CSS animation plus a pause control, honouring prefers-reduced-motion."}, {"pattern": "<(video|audio)\\b[^>]*\\bautoplay\\b", "flags": "i", "sev": "warn", "message": "EN 301 549 V3.2.1 §9.2.2.2 (in force) — media autoplays; anything over 5 seconds needs a pause/stop control.", "fix": "Drop autoplay, or keep it muted, under 5s, and give a visible pause button."}, {"pattern": ">\\s*(click here|read more|learn more|more|here|link|this link)\\s*<", "flags": "i", "sev": "warn", "message": "EN 301 549 V3.2.1 §9.2.4.4 (in force) — the link text does not say where it goes when read out of context.", "fix": "<a href=\"/refunds\">Read the refund policy</a>"}, {"pattern": "outline\\s*:\\s*(none|0)\\b", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.2.4.7 (in force) — removing the outline removes the visible focus indicator.", "fix": ":focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }"}, {"pattern": "<html(?![^>]*\\slang\\s*=)[^>]*>", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.3.1.1 (in force) — <html> has no lang, so a screen reader picks the wrong voice.", "fix": "<html lang=\"de\"> — the language actually used on the page."}, {"pattern": "<(input|select|textarea)(?![^>]*\\bid\\s*=)(?![^>]*aria-label)(?![^>]*aria-labelledby)(?![^>]*type\\s*=\\s*[\"\\'](hidden|submit|button|reset|image)[\"\\'])[^>]*>", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.3.3.2 (in force) — field has no id to label, and no aria-label either. A placeholder is not a label.", "fix": "<label for=\"vat\">VAT number</label><input id=\"vat\" name=\"vat\">"}, {"pattern": "<iframe(?![^>]*\\stitle\\s*=)[^>]*>", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.4.1.2 (in force) — <iframe> has no title, so it is announced only as \"frame\".", "fix": "<iframe title=\"Payment form\" src=\"…\">"}, {"pattern": "<(?:a\\b(?=[^>]*\\shref\\s*=)|(?:button|input|select|textarea)\\b)(?![^>]*\\stabindex\\s*=\\s*[\"\\']?-1)(?![^>]*\\sdisabled(?=[\\s=/>]|$))[^>]*aria-hidden\\s*=\\s*[\"\\']true", "flags": "i", "sev": "error", "message": "EN 301 549 V3.2.1 §9.4.1.2 (in force) — a focusable element hidden from the accessibility tree: reachable by Tab, invisible to a screen reader.", "fix": "Remove aria-hidden, or take it out of the tab order too with tabindex=\"-1\"."}, {"pattern": "<button(?![^>]*aria-label)(?![^>]*title\\s*=)[^>]*>\\s*<(svg|i|img)\\b", "flags": "i", "sev": "warn", "message": "EN 301 549 V3.2.1 §9.4.1.2 (in force) — icon-only button with no accessible name.", "fix": "<button aria-label=\"Close dialog\"><svg aria-hidden=\"true\">…</svg></button>"}, {"pattern": "position\\s*:\\s*(sticky|fixed)", "flags": "i", "sev": "info", "message": "EN 301 549 V4.1.1 §9.2.4.11 (WCAG 2.2 — not in force yet; OJ citation expected Oct 2026) — a sticky bar can cover the focused element. Not a violation today.", "fix": "scroll-margin-block on focusable content, or shrink the bar on keyboard focus."}, {"pattern": "<(button|a)\\b[^>]*style\\s*=\\s*[\"\\'][^\"\\']*(width|height)\\s*:\\s*(1?\\d|2[0-3])px", "flags": "i", "sev": "info", "message": "EN 301 549 V4.1.1 §9.2.5.8 (WCAG 2.2 — not in force yet; OJ citation expected Oct 2026) — target smaller than 24 by 24 CSS pixels. Not a violation today.", "fix": "min-width: 24px; min-height: 24px — or leave 24px of clear space around it."}, {"pattern": "onpaste\\s*=\\s*[\"\\']?\\s*(return\\s+false|false)", "flags": "i", "sev": "info", "message": "EN 301 549 V4.1.1 §9.3.3.8 (WCAG 2.2 — not in force yet; OJ citation expected Oct 2026) — blocking paste stops password managers. Not a violation today.", "fix": "Remove the onpaste handler; let the field accept a pasted value."}, {"pattern": "<input(?=[^>]*type\\s*=\\s*[\"\\']password[\"\\'])(?![^>]*autocomplete)[^>]*>", "flags": "i", "sev": "info", "message": "EN 301 549 V4.1.1 §9.3.3.8 (WCAG 2.2 — not in force yet; OJ citation expected Oct 2026) — password field with no autocomplete token. Not a violation today.", "fix": "autocomplete=\"current-password\" on sign-in, \"new-password\" on sign-up."}, {"pattern": "\\b(g-recaptcha|h-captcha|hcaptcha|recaptcha)\\b", "flags": "i", "sev": "info", "message": "EN 301 549 V4.1.1 §9.3.3.8 (WCAG 2.2 — not in force yet; OJ citation expected Oct 2026) — a cognitive function test in authentication needs an alternative. Not a violation today.", "fix": "Offer a non-puzzle route: email link, passkey, or an object-recognition alternative."}]; | |
| function scan(t){var L=String(t).split(/\r?\n/),h=[],i,r,re; | |
| for(i=0;i<L.length;i++){for(var k=0;k<RULES.length;k++){r=RULES[k]; | |
| try{re=new RegExp(r.pattern,r.flags||'')}catch(e){continue} | |
| if(re.test(L[i]))h.push({line:i+1,msg:r.message})}} | |
| return h} | |
| document.getElementById('go').onclick=function(){ | |
| var t=document.getElementById('src').value,o=document.getElementById('out'); | |
| if(!t.trim()){o.innerHTML='';return} | |
| var h=scan(t),c=document.getElementById('cta'); | |
| if(c)c.hidden=false; | |
| if(!h.length){o.innerHTML='<div class="ok">No EN 301 549 finding on these lines. That covers the clauses this linter can see in markup — it does not cover keyboard traps, focus order or anything that needs the page running.</div>';return} | |
| var s='<table><tr><th>Line</th><th>Finding</th></tr>'; | |
| for(var i=0;i<h.length;i++){s+='<tr><td class="n">'+h[i].line+'</td><td>'+ | |
| String(h[i].msg).replace(/&/g,'&').replace(/</g,'<')+'</td></tr>'} | |
| o.innerHTML=s+'</table>'}; | |
| </script> | |
| </html> | |
| <section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://open-vsx.org/extension/readystack/en301549-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'en301549-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/en301549-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'en301549-lint',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://www.npmjs.com/package/@readystack/en301549-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'en301549-lint',src:'mcp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/en301549-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'en301549-lint',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">npx @readystack/en301549-lint <file> | |
| npx @readystack/en301549-lint --mcp | |
| docker run --rm -v "$PWD:/w" getreadystack/en301549-lint /w</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section> | |
| <section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 28 rules EN 301 549 Lint — European Accessibility Act rules, with the clause number, inside your editor checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"en301549-lint",lang:"en",source:"rule_table"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section> | |
| <section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Flags the EN 301 549 clause each line breaks, and separates what is in force today from what arrives with V4.1.1.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A manual WCAG audit is quoted at $100–$250 per page, and $1,250–$2,750 for a small site (Accessible.org, 2026).</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_9o33w7ZEWFvtHnsjAVCGBPT5ubS1CZsHl7iec0wNhUa" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section> | |
| <!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script><script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script> | |
| <script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script> | |
| <section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-09">Updated 2026-09-09</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-09","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku23278_result_card.jpg" alt="EN 301 549 Lint — European Accessibility Act rules, with the clause number, inside your editor" loading="lazy" style="max-width:100%;border-radius:6px"></p> | |
| <p>Five lines in a German checkout template break EN 301 549 — and in the Irish sign-in page next door, half the findings are not law until October.</p> | |
| <p>That last part is the one nobody gets right, including every chatbot I asked. The European Accessibility Act has applied to new e-commerce, banking, booking and e-book services in the EU since 28 June 2025. But the technical standard actually cited in the Official Journal — Implementing Decision (EU) 2021/1339 — is <strong>EN 301 549 V3.2.1</strong>, and V3.2.1 is <strong>WCAG 2.1</strong> AA. Version 4.1.1, the one that folds WCAG 2.2 in, is expected to be cited around <strong>October 2026</strong>. Until then, a WCAG 2.2 criterion is good practice, not an obligation.</p> | |
| <p>So a tool that reports "WCAG 2.2 failure" in September 2026 is telling you something true about accessibility and something false about your legal exposure. Those are different questions and they have different budgets.</p> | |
| <h3>The five lines</h3> | |
| <pre><code><html> <!-- §9.3.1.1 no lang --> | |
| <img src="/logo.png"> <!-- §9.1.1.1 no alt --> | |
| <div onclick="pay()">Jetzt kaufen</div> <!-- §9.2.1.1 not keyboard operable --> | |
| <span style="color: #999">inkl. MwSt.</span> <!-- §9.1.4.3 2.85:1, AA needs 4.5:1 --> | |
| <iframe src="/sepa"></iframe> <!-- §9.4.1.2 no title --></code></pre> | |
| <p>Fixed:</p> | |
| <pre><code><html lang="de"> | |
| <img src="/logo.png" alt="Acme GmbH home"> | |
| <button type="button" onclick="pay()">Jetzt kaufen</button> | |
| <span style="color: #767676">inkl. MwSt.</span> <!-- 4.54:1 --> | |
| <iframe title="Payment form" src="/sepa"></iframe></code></pre> | |
| <p><code>#767676</code> is worth memorising: it is the lightest grey that still clears 4.5:1 on white. <code>#777</code> is 4.48:1 and fails by two hundredths. Tailwind's <code>text-gray-400</code> is about 2.8:1 and fails outright; <code>text-gray-600</code> is 7.5:1.</p> | |
| <h3>What the "not yet" bucket contains</h3> | |
| <p>WCAG 2.2 added nine success criteria in total. Four of them are what this linter can see in markup, so they sit in its not-yet bucket — worth fixing early, but not to be reported to your compliance officer as violations today:</p> | |
| <ul><li><strong>§9.2.4.11</strong> focus not obscured — a sticky header covering the focused element</li><li><strong>§9.2.5.8</strong> target size — anything under 24 by 24 CSS pixels</li><li><strong>§9.3.3.7</strong> redundant entry</li><li><strong>§9.3.3.8</strong> accessible authentication — this is the one that catches CAPTCHAs and <code>onpaste="return false"</code> on password fields</li></ul> | |
| <p>If you are shipping a sign-in page in the EU, §9.3.3.8 is the one to plan for. It is not enforceable this morning. It very likely is next year.</p> | |
| <h3>Where the free tools stop</h3> | |
| <p>axe-core and <code>eslint-plugin-jsx-a11y</code> are good and you should keep using them. They report WCAG success criteria. What they do not print is the EN 301 549 clause number, which is the vocabulary of the audit, the accessibility statement, and the market surveillance authority in each member state. And they do not distinguish the standard in force from the standard incoming, because from a pure accessibility standpoint that distinction does not matter — it only matters when someone asks what you are legally required to have fixed by when.</p> | |
| <p>That gap is a two-column table, and that table is the extension.</p> | |
| <h3>The microenterprise line</h3> | |
| <p>One more thing the chatbots get backwards: the exemption for fewer than 10 employees and under €2 million turnover applies to <strong>services, not products</strong>. If you ship a product covered by the Act, being small does not exempt you. Check which side of that line your company is on before you decide the deadline is not yours.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku23278_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku23278_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does EN 301 549 Lint actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads the markup file you have open in VS Code and flags every line that breaks one of 28 accessibility rules, printing the EN 301 549 clause number and a corrected version of the line. It also marks which findings are enforceable under the standard cited today, EN 301 549 V3.2.1, and which only arrive with V4.1.1.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is EN 301 549 Lint for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">EN 301 549 Lint is for front-end and full-stack developers shipping e-commerce, banking, booking, ticketing or e-book services into the European Union, where the European Accessibility Act has applied to new services since 28 June 2025. It is also for whoever writes the accessibility statement and needs clause numbers rather than generic WCAG labels.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why is axe or eslint-plugin-jsx-a11y not enough for EU compliance?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Those tools are good and worth keeping. They report WCAG success criteria, not EN 301 549 clause numbers, which is the vocabulary an EU auditor and a market surveillance authority use. They also treat WCAG 2.2 as current, while the standard cited in the Official Journal is still V3.2.1, which is WCAG 2.1 AA. That difference decides what you are legally required to have fixed.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what does the licence add?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free covers the file you have open: all 28 rules, every clause number, and a corrected line for each finding, with no watermark, trial or usage counter. The $29 licence changes the scope, not the quality. It scans every template in the repository, exports a dated evidence file with file, line, clause and severity, and prints machine-readable JSON so CI can fail a build.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">How does this compare to paying for an accessibility audit?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A manual WCAG audit is quoted at roughly $100 to $250 per page, and $1,250 to $2,750 for a small site, according to Accessible.org's published 2026 pricing. This extension costs $29 once. It does not replace that audit: it cannot judge focus order, keyboard traps or anything needing the page running. It removes the findings a human should never have been billed to find.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. EN 301 549 Lint — EU Accessibility Act checks the file you open against 28 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does EN 301 549 Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads the markup file you have open in VS Code and flags every line that breaks one of 28 accessibility rules, printing the EN 301 549 clause number and a corrected version of the line. It also marks which findings are enforceable under the standard cited today, EN 301 549 V3.2.1, and which only arrive with V4.1.1."}}, {"@type": "Question", "name": "Who is EN 301 549 Lint for?", "acceptedAnswer": {"@type": "Answer", "text": "EN 301 549 Lint is for front-end and full-stack developers shipping e-commerce, banking, booking, ticketing or e-book services into the European Union, where the European Accessibility Act has applied to new services since 28 June 2025. It is also for whoever writes the accessibility statement and needs clause numbers rather than generic WCAG labels."}}, {"@type": "Question", "name": "Why is axe or eslint-plugin-jsx-a11y not enough for EU compliance?", "acceptedAnswer": {"@type": "Answer", "text": "Those tools are good and worth keeping. They report WCAG success criteria, not EN 301 549 clause numbers, which is the vocabulary an EU auditor and a market surveillance authority use. They also treat WCAG 2.2 as current, while the standard cited in the Official Journal is still V3.2.1, which is WCAG 2.1 AA. That difference decides what you are legally required to have fixed."}}, {"@type": "Question", "name": "What is free and what does the licence add?", "acceptedAnswer": {"@type": "Answer", "text": "Free covers the file you have open: all 28 rules, every clause number, and a corrected line for each finding, with no watermark, trial or usage counter. The $29 licence changes the scope, not the quality. It scans every template in the repository, exports a dated evidence file with file, line, clause and severity, and prints machine-readable JSON so CI can fail a build."}}, {"@type": "Question", "name": "How does this compare to paying for an accessibility audit?", "acceptedAnswer": {"@type": "Answer", "text": "A manual WCAG audit is quoted at roughly $100 to $250 per page, and $1,250 to $2,750 for a small site, according to Accessible.org's published 2026 pricing. This extension costs $29 once. It does not replace that audit: it cannot judge focus order, keyboard traps or anything needing the page running. It removes the findings a human should never have been billed to find."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. EN 301 549 Lint — EU Accessibility Act checks the file you open against 28 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. For a filing, an audit or a client you need that dated result on your own files."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:23278,slug:"en301549-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/accessibility-statement-lint"><span class="yj-rel-t">Accessibility Statement Lint (EU model)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/dsa-terms-lint"><span class="yj-rel-t">DSA Terms Lint - EU Digital Services Act</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/data-act-switching-lint"><span class="yj-rel-t">EU Data Act Switching Clause Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/wcag21-aa-legal-baseline-audit"><span class="yj-rel-t">WCAG 2.1 AA Legal Baseline Audit for templates</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/robots-txt-ai-crawler-audit"><span class="yj-rel-t">AI Crawler Rules - robots.txt Audit for AI Search</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/ai-disclosure-lint-sb942"><span class="yj-rel-t">AI Output Disclosure Lint SB 942</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section> |