<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: perceivable</title>
    <description>The latest articles on DEV Community by perceivable (@perceivable).</description>
    <link>https://dev.to/perceivable</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4161498%2F89c4ad90-ed41-42cc-a1f9-41528ea82e13.png</url>
      <title>DEV Community: perceivable</title>
      <link>https://dev.to/perceivable</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/perceivable"/>
    <language>en</language>
    <item>
      <title>I turned my accessibility checker into an MCP server, so the agent that writes the HTML can also fix it</title>
      <dc:creator>perceivable</dc:creator>
      <pubDate>Sun, 11 Oct 2026 03:46:58 +0000</pubDate>
      <link>https://dev.to/perceivable/i-turned-my-accessibility-checker-into-an-mcp-server-so-the-agent-that-writes-the-html-can-also-1n7i</link>
      <guid>https://dev.to/perceivable/i-turned-my-accessibility-checker-into-an-mcp-server-so-the-agent-that-writes-the-html-can-also-1n7i</guid>
      <description>&lt;p&gt;Last week I wrote about &lt;a href="https://dev.to/perceivable/i-ran-my-accessibility-checker-against-govuk-it-found-72-violations-all-of-them-were-mine-5fp0"&gt;running my WCAG checker against gov.uk&lt;/a&gt; and finding that every one of the 72 "violations" was a bug in my checker. Fixing those left me with an engine I trust on real sites, packaged as a Chrome extension.&lt;/p&gt;

&lt;p&gt;But the people who most need an accessibility check right now are not people. They are coding agents. Claude Code, Cursor and friends generate a huge share of new HTML, and they ship &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; without &lt;code&gt;alt&lt;/code&gt;, grey-on-white placeholders and &lt;code&gt;outline: none&lt;/code&gt; just like we did, only faster. An extension that a human clicks does nothing for that.&lt;/p&gt;

&lt;p&gt;So the same engine is now an MCP server: &lt;code&gt;a11yscope-mcp&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Three tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scan_page&lt;/code&gt; loads a URL or a local HTML file in headless Chrome and returns violations grouped by rule. Every finding has a CSS selector, an HTML snippet and a message that says what to change.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scan_html&lt;/code&gt; does the same for a string of markup, which is the one agents actually need: &lt;em&gt;you just wrote this component, check it before you save it.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list_rules&lt;/code&gt; returns the 31 checks with the WCAG 2.2 success criteria they map to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The checks cover text alternatives (images, labels, buttons, links, frames, SVG), colour contrast with the large-text and bold exemptions applied and translucent backgrounds composited, document structure (language, title, heading order, main landmark, skip link, tables, lists), and keyboard, pointer and ARIA (zoom lock, positive tabindex, aria-hidden focusables, nested controls, the new 24×24 target size, autoplay, captions, invalid roles, required ARIA states, autocomplete, removed focus outlines).&lt;/p&gt;

&lt;p&gt;Everything runs locally. It uses the Chrome, Chromium, Edge or Brave you already have, downloads nothing at install time, sends nothing anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add a11yscope &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; a11yscope-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor, Claude Desktop or any other MCP client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"a11yscope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a11yscope-mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a prompt like &lt;em&gt;"scan &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; with a11yscope and fix every violation, then scan again"&lt;/em&gt; closes the loop: the agent reads the selector and the message, edits the file, and re-scans until the list is empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a result looks like
&lt;/h2&gt;

&lt;p&gt;For a component with an unlabelled image and an empty link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"violations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"review"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"rulesRun"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"rulesFailed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"rulesPassed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"img-alt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"wcag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"1.1.1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"impact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"critical"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"help"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add alt text describing what the image conveys. If the image is purely decorative, use alt=&lt;/span&gt;&lt;span class="se"&gt;\"\"&lt;/span&gt;&lt;span class="s2"&gt; ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"violations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"html &amp;gt; body &amp;gt; main &amp;gt; img"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;img&amp;gt; has no alt attribute (src: a.png)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"snippet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;img src=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;a.png&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; width=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;120&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; height=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;80&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"link-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"wcag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"2.4.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.1.2"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"impact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"critical"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"violations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"html &amp;gt; body &amp;gt; main &amp;gt; a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Link has no discernible text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"snippet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;a href=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;/x&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"disclaimer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Automated checks find roughly a third of accessibility barriers. A clean result is a good sign, not a conformance claim; items marked review need a human decision."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That disclaimer is in every response on purpose. An agent will happily tell its user "the page is now WCAG compliant" if you let it. Roughly a third of barriers are machine-checkable; whether the alt text is &lt;em&gt;true&lt;/em&gt;, whether the page makes sense in a screen reader, whether a keyboard user can finish the checkout, no tool can say. Anything the engine cannot decide comes back as &lt;code&gt;review&lt;/code&gt;, not as pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calibration
&lt;/h2&gt;

&lt;p&gt;The gov.uk exercise became the acceptance test. gov.uk, webaim.org, deque.com, a11yproject.com and w3.org/WAI all return zero violations from this engine. Anything reported on those sites is treated as my bug until proven otherwise. If you hit a false positive, the issue tracker is the most useful place to put it: &lt;a href="https://github.com/perceivable/a11yscope/issues" rel="noopener noreferrer"&gt;https://github.com/perceivable/a11yscope/issues&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;npm: &lt;a href="https://www.npmjs.com/package/a11yscope-mcp" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/a11yscope-mcp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>mcp</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I ran my accessibility checker against gov.uk. It found 72 violations — all of them were mine.</title>
      <dc:creator>perceivable</dc:creator>
      <pubDate>Sun, 04 Oct 2026 11:46:55 +0000</pubDate>
      <link>https://dev.to/perceivable/i-ran-my-accessibility-checker-against-govuk-it-found-72-violations-all-of-them-were-mine-5fp0</link>
      <guid>https://dev.to/perceivable/i-ran-my-accessibility-checker-against-govuk-it-found-72-violations-all-of-them-were-mine-5fp0</guid>
      <description>&lt;p&gt;I built a Chrome extension that checks pages against the automatable parts of WCAG 2.2 AA. This post isn't really about the extension. It's about what happened when I stopped trusting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The control group
&lt;/h2&gt;

&lt;p&gt;Once the checker worked, the first thing I did was point it at &lt;strong&gt;gov.uk&lt;/strong&gt;. The UK government's site has a long, public record of taking accessibility seriously. If my tool reported problems there, the sensible assumption was that the tool was wrong, not the site.&lt;/p&gt;

&lt;p&gt;It reported &lt;strong&gt;72 violations&lt;/strong&gt;. Every one of them was a bug in my code.&lt;/p&gt;

&lt;p&gt;So I made that the method. I picked sites built by people who do accessibility for a living — gov.uk, WebAIM, Deque, The A11Y Project, the W3C's WAI pages — and treated anything reported on them as my bug until proven otherwise.&lt;/p&gt;

&lt;p&gt;Six real defects came out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. I skipped the WCAG 2.5.8 Spacing exception
&lt;/h2&gt;

&lt;p&gt;2.5.8 Target Size (Minimum) is new in WCAG 2.2: pointer targets should be at least 24×24 CSS pixels. The naive implementation is one line — read &lt;code&gt;getBoundingClientRect()&lt;/code&gt;, compare against 24.&lt;/p&gt;

&lt;p&gt;But the criterion has five exceptions, and one of them does most of the work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spacing: an undersized target passes if a 24px-diameter circle centred on it doesn't intersect another target, or another undersized target's circle.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The real question 2.5.8 asks isn't "is it big enough?" — it's "is there room to miss without hitting something else?" A 20px icon button with generous margin is fine. Three 16px buttons packed 2px apart are not.&lt;/p&gt;

&lt;p&gt;Without the spacing check: 72 violations on gov.uk. With it: &lt;strong&gt;zero&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. I measured contrast on text nobody can see
&lt;/h2&gt;

&lt;p&gt;This is the standard way to give an icon button a screen reader label:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.icon-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-indent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-5000px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The text is in the DOM but never painted. I was faithfully measuring its colour contrast — which is meaningless.&lt;/p&gt;

&lt;p&gt;The fix: measure the &lt;strong&gt;text node&lt;/strong&gt; with a &lt;code&gt;Range&lt;/code&gt;, not the element's box, and check that its rect survives every ancestor with &lt;code&gt;overflow&lt;/code&gt; clipping. If it's clipped away, there's nothing to measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. My accessible-name computation ignored &lt;code&gt;aria-label&lt;/code&gt; on descendants
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/nvidia"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"img"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Nvidia"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That link is correctly named. My code collected text nodes only, found none, and reported it as a link with no discernible text.&lt;/p&gt;

&lt;p&gt;In the accname spec, "name from content" recursively takes each descendant's &lt;em&gt;accessible name&lt;/em&gt;, not its raw text. That one bug produced &lt;strong&gt;30 false findings on stripe.com&lt;/strong&gt; alone. Logo links and icon links are everywhere — shipped as-is, it would have cried wolf on most of the web.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. I called a zero-height element "too small"
&lt;/h2&gt;

&lt;p&gt;On a11yproject.com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Target is 320×0px, below the 24×24 minimum
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A link inside a collapsed panel. Something with no height doesn't have a small hit area — it has none. The message was describing a box, not a target.&lt;/p&gt;

&lt;p&gt;Now the hit area is the union of the element's box and its rendered descendants (clicks on a child are routed to the enclosing link), and if that union is still degenerate, the element is skipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. I told Deque — who make axe — that their site had six violations
&lt;/h2&gt;

&lt;p&gt;The rule: "focusable element inside &lt;code&gt;aria-hidden&lt;/code&gt;". A keyboard user can reach it, a screen reader user can't perceive it — a real and common bug.&lt;/p&gt;

&lt;p&gt;Except every one of the six was inside a &lt;code&gt;display: none&lt;/code&gt; subtree. A closed dropdown. Those elements aren't in the tab order at all; the browser skips them just as the screen reader does. No mismatch.&lt;/p&gt;

&lt;p&gt;The distinction matters because the &lt;em&gt;real&lt;/em&gt; version of this bug is extremely common: an off-canvas menu shoved off-screen with a transform, still &lt;code&gt;display: block&lt;/code&gt;, still tabbable, marked &lt;code&gt;aria-hidden&lt;/code&gt;. Tab into it and focus vanishes. That one the tool should — and now does — still catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. I read 2.4.4 as if it were AAA
&lt;/h2&gt;

&lt;p&gt;I was failing links like "Read more" and "Learn more".&lt;/p&gt;

&lt;p&gt;But 2.4.4 is &lt;strong&gt;Link Purpose (In Context)&lt;/strong&gt;. A "Read more" whose purpose is clear from the card it sits in conforms. Requiring the link text to stand on its own is &lt;strong&gt;2.4.9&lt;/strong&gt;, which is Level AAA.&lt;/p&gt;

&lt;p&gt;This wasn't a code bug; it was a misreading of the spec. I suspect it's a common one. It's now reported as "needs review", not as a failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Site&lt;/th&gt;
&lt;th&gt;Violations reported&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gov.uk&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;webaim.org&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deque.com&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a11yproject.com&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;w3.org/WAI&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sites with genuine problems still light up — which is the other half of the test.&lt;/p&gt;

&lt;p&gt;Every fix has a regression test. The one that actually keeps the tool honest is a single file, &lt;code&gt;test/clean.html&lt;/code&gt;: a correctly built page that &lt;strong&gt;must produce zero violations&lt;/strong&gt;. Anything flagged there is a false positive by definition.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took from it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When an automated accessibility report contradicts a site with real expertise behind it, check the tool first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And be honest about what automation can do at all. Automated checks find roughly a third of accessibility barriers. Whether alt text is accurate, whether a keyboard user can finish a checkout, whether a page makes sense read aloud — no tool answers those. So this one reports what it can't decide as "needs review" instead of guessing, and says so in its UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to try it
&lt;/h2&gt;

&lt;p&gt;It's free, has no account, and makes &lt;strong&gt;no network calls at all&lt;/strong&gt; — the privacy policy depends on that, so there's a test that fails the build if anyone adds one. The source is public.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome Web Store: &lt;a href="https://chromewebstore.google.com/detail/ldbmeaihgdlghenedbhafkfikefnmicb" rel="noopener noreferrer"&gt;https://chromewebstore.google.com/detail/ldbmeaihgdlghenedbhafkfikefnmicb&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/perceivable/a11yscope" rel="noopener noreferrer"&gt;https://github.com/perceivable/a11yscope&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The 2.5.8 exceptions, in detail: &lt;a href="https://perceivable.github.io/guides/wcag-target-size/" rel="noopener noreferrer"&gt;https://perceivable.github.io/guides/wcag-target-size/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If you find it flagging correct markup, please tell me.&lt;/strong&gt; A scanner that cries wolf is worse than no scanner, and every report so far has become a regression test.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
