<?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: Auditready</title>
    <description>The latest articles on DEV Community by Auditready (@auditready).</description>
    <link>https://dev.to/auditready</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%2F4150759%2F0954b31a-327c-44b5-a76a-db37be957faa.png</url>
      <title>DEV Community: Auditready</title>
      <link>https://dev.to/auditready</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/auditready"/>
    <language>en</language>
    <item>
      <title>The 12 HTML checks my accessibility scanner runs — and what each one can't see</title>
      <dc:creator>Auditready</dc:creator>
      <pubDate>Thu, 01 Oct 2026 18:36:17 +0000</pubDate>
      <link>https://dev.to/auditready/the-12-html-checks-my-accessibility-scanner-runs-and-what-each-one-cant-see-4d64</link>
      <guid>https://dev.to/auditready/the-12-html-checks-my-accessibility-scanner-runs-and-what-each-one-cant-see-4d64</guid>
      <description>&lt;h1&gt;
  
  
  The 12 HTML checks my accessibility scanner runs — and what each one can't see
&lt;/h1&gt;

&lt;p&gt;I build AuditReady, an accessibility scanner that runs 12 deterministic checks over a page's HTML and turns the findings into a report an agency can hand a client. Same product as &lt;a href="https://dev.to/auditready/i-built-an-accessibility-scanner-thats-honest-about-what-it-cant-see-jpo"&gt;my first post here&lt;/a&gt;, so the disclosure stands: it's mine. Here is what each check decides, and where it runs out of road. Every check looks for one pattern, counts how often it occurs, keeps the offending elements and their position, and names the WCAG 2.2 criterion it maps to. Nothing is model-generated.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Missing alt text — 1.1.1 (A)
&lt;/h3&gt;

&lt;p&gt;A screen reader announces the file name, or just "graphic", where an image carried meaning. It flags an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; with no &lt;code&gt;alt&lt;/code&gt; attribute (&lt;code&gt;alt=""&lt;/code&gt; is valid for decorative images and is never reported) and image buttons with no alt. It cannot judge the alt that exists: &lt;code&gt;alt="image"&lt;/code&gt; passes.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Form fields with no label — 3.3.2 (A)
&lt;/h3&gt;

&lt;p&gt;The visitor hears "edit text" and does not know what to type. It flags a visible input, select or textarea with no &lt;code&gt;&amp;lt;label for&amp;gt;&lt;/code&gt;, wrapping label, &lt;code&gt;aria-label&lt;/code&gt;, &lt;code&gt;aria-labelledby&lt;/code&gt; or &lt;code&gt;title&lt;/code&gt; — &lt;code&gt;&amp;lt;input placeholder="Email"&amp;gt;&lt;/code&gt; included, because placeholder text is not a label. It cannot judge whether the label is clear, or whether errors are explained in text.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Heading structure — 1.3.1 (A)
&lt;/h3&gt;

&lt;p&gt;People navigate by headings, so the outline has to hold together. Three findings: no &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;, more than one (often a logo marked up as one), and a skip such as &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; straight to &lt;code&gt;&amp;lt;h4&amp;gt;&lt;/code&gt;. It cannot see whether the heading text says anything useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Page title — 2.4.2 (A)
&lt;/h3&gt;

&lt;p&gt;The title is the first thing announced and how tabs and bookmarks are told apart. It flags a missing or empty &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; — &lt;code&gt;&amp;lt;title&amp;gt;&amp;lt;/title&amp;gt;&lt;/code&gt; is announced as "untitled document". It cannot tell whether the title is descriptive or unique: the same "Home" on forty pages passes.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Page language — 3.1.1 (A)
&lt;/h3&gt;

&lt;p&gt;Without a declared language a screen reader guesses at pronunciation. It flags &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; with no &lt;code&gt;lang&lt;/code&gt;, or an empty one. It reads only the root element, so it cannot see a German quotation inside an English page that needs its own &lt;code&gt;lang&lt;/code&gt;, or whether the code present is right.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Vague link text — 2.4.4 (A)
&lt;/h3&gt;

&lt;p&gt;Screen-reader users pull up a list of links out of context, where "click here" twelve times says nothing. It flags links whose whole label is a known vague phrase or a bare URL; a bare domain in a logo link, like &lt;code&gt;python.org&lt;/code&gt;, counts as a brand name. It cannot judge context: "Learn more" inside a card may read perfectly.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Pinch-zoom switched off — 1.4.4 (AA)
&lt;/h3&gt;

&lt;p&gt;Someone with low vision pinches to enlarge the text and nothing happens. It flags a viewport meta tag with &lt;code&gt;user-scalable=no&lt;/code&gt; or &lt;code&gt;maximum-scale&lt;/code&gt; below 2: &lt;code&gt;&amp;lt;meta name="viewport" content="user-scalable=no, maximum-scale=1"&amp;gt;&lt;/code&gt;. It cannot test what happens when the page is zoomed.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Icon buttons with no name — 4.1.2 (A)
&lt;/h3&gt;

&lt;p&gt;The screen reader says "button" and stops, and the icon's meaning is gone. It flags links and buttons whose computed accessible name is empty, checking &lt;code&gt;aria-labelledby&lt;/code&gt;, &lt;code&gt;aria-label&lt;/code&gt;, inner text, an image alt and an SVG title in turn, so &lt;code&gt;&amp;lt;button aria-label="Search"&amp;gt;…&amp;lt;/button&amp;gt;&lt;/code&gt; is left alone. It cannot judge the name it finds, or whether a toggle exposes its state.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Iframes with no title — 4.1.2 (A)
&lt;/h3&gt;

&lt;p&gt;A frame is a whole document inside the page; untitled, the visitor lands inside someone's video or booking form with no explanation. It flags &lt;code&gt;&amp;lt;iframe src="…"&amp;gt;&lt;/code&gt; with no non-empty title and no other name. It cannot see a single element inside the frame — third-party embeds are usually the weakest part.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Duplicate id values — 4.1.2 (A)
&lt;/h3&gt;

&lt;p&gt;When an id repeats, &lt;code&gt;&amp;lt;label for&amp;gt;&lt;/code&gt; and &lt;code&gt;aria-labelledby&lt;/code&gt; can point at the wrong element and in-page links land in the wrong place — usually a copy-pasted form block. It reports the repeated placements. It cannot tell whether anything actually breaks, since the page's JavaScript may never touch that id. (WCAG 2.2 dropped the old 4.1.1 Parsing criterion; this is reported as a reference problem.)&lt;/p&gt;

&lt;h3&gt;
  
  
  11. Positive tabindex — 2.4.3 (A)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;tabindex="1"&lt;/code&gt; overrides the page's reading order, so tabbing jumps and skips controls, and the order breaks when someone adds an element. It flags any positive tabindex; &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;-1&lt;/code&gt; are fine. It cannot measure the order that results — a page with no positive values can still tab in a nonsense order.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Tables with no headers — 1.3.1 (A)
&lt;/h3&gt;

&lt;p&gt;A table without headers announces "£40, £40, £40" instead of "Price, £40". It flags tables that contain data cells but no &lt;code&gt;&amp;lt;th&amp;gt;&lt;/code&gt;, and skips layout tables marked &lt;code&gt;role="presentation"&lt;/code&gt;. It cannot check whether the headers are scoped correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What none of the twelve can see
&lt;/h2&gt;

&lt;p&gt;Together they are a scanner that reads markup. Everything needing a browser, an ear or a judgement sits outside it. Verbatim from my site: "Automated tools catch only a minority of WCAG issues. AuditReady is not a compliance guarantee and not legal advice — a manual review by a person is still required."&lt;/p&gt;

&lt;p&gt;That is most of the job, not a footnote. From the 13-item checklist the full report ships with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contrast measured (4.5:1, 3:1 for large text) and non-text contrast for icons, borders and focus rings.&lt;/li&gt;
&lt;li&gt;Focus visibility, focus order, keyboard traps.&lt;/li&gt;
&lt;li&gt;Custom widgets: operable without a mouse, with name, role, state and status announced.&lt;/li&gt;
&lt;li&gt;Errors described in text, beside the field, with a suggestion.&lt;/li&gt;
&lt;li&gt;200% zoom, 320px reflow and text-spacing overrides; a working skip link.&lt;/li&gt;
&lt;li&gt;Captions, transcripts and audio description; anything that moves, blinks or autoplays; time limits.&lt;/li&gt;
&lt;li&gt;Drag alternatives, 24×24 tap targets and single-pointer equivalents for multi-point gestures.&lt;/li&gt;
&lt;li&gt;Cookie banners, chat widgets, booking engines and PDFs.&lt;/li&gt;
&lt;li&gt;Everything behind a login, and whether the alt text, link text and titles that exist are accurate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More than twenty distinct things, none settled by a rule over a string of HTML. Two limits apply on top: no JavaScript is executed, so a client-rendered page shows fewer findings than it has; and one page is a snapshot, not a crawl.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why start here
&lt;/h2&gt;

&lt;p&gt;A deterministic check is cheap, repeatable and honest about what it did: mechanical failures found before a person spends an afternoon on them, and a list you can verify. &lt;a href="https://feaefe371424d0725b266468856c9101.ctonew.app/#scan" rel="noopener noreferrer"&gt;The free scan&lt;/a&gt; runs all twelve and shows the top five finding groups; the full report (one-off $29 per site) adds every finding, your agency's name and the coverage table. If you run a page you know well and hit a false positive, tell me in the comments.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
    <item>
      <title>I built an accessibility scanner that's honest about what it can't see</title>
      <dc:creator>Auditready</dc:creator>
      <pubDate>Tue, 29 Sep 2026 18:15:49 +0000</pubDate>
      <link>https://dev.to/auditready/i-built-an-accessibility-scanner-thats-honest-about-what-it-cant-see-jpo</link>
      <guid>https://dev.to/auditready/i-built-an-accessibility-scanner-thats-honest-about-what-it-cant-see-jpo</guid>
      <description>&lt;h1&gt;
  
  
  I built an accessibility scanner that's honest about what it can't see
&lt;/h1&gt;

&lt;p&gt;I build AuditReady, an accessibility scanner that runs 12 deterministic checks on a page's HTML and turns the findings into a report an agency can hand a client. This is a maker post, so the disclosure goes first: it's my product, and you should read the rest with that in mind. Most of what follows is about what it can't do, because that's the part worth getting right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap isn't detection, it's the deliverable
&lt;/h2&gt;

&lt;p&gt;Agencies keep asking for "an accessibility report". What the free tools hand back is a list aimed at whoever will fix the issue: rule names, DOM nodes, a count, sometimes a score. That's the right output for a developer mid-sprint, and WAVE, Lighthouse, axe, Accessibility Insights and Pa11y are all good at it — and free. The gap is the document. A bare count of issues isn't something a client can act on or sign off, and it doesn't say what was tested, what was found, or what still needs a person.&lt;/p&gt;

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

&lt;p&gt;You paste one page address. The server fetches that page's HTML as a public visitor would and runs 12 checks over it: missing alt text; form fields with no label; heading structure (exactly one &lt;code&gt;h1&lt;/code&gt;, no skipped levels); a missing or empty page title; a missing &lt;code&gt;lang&lt;/code&gt; attribute; link text like "click here" or "read more"; pinch-zoom switched off in the viewport meta tag; icon buttons and links with no accessible name; iframes with no title; duplicate &lt;code&gt;id&lt;/code&gt; values; positive &lt;code&gt;tabindex&lt;/code&gt;; and data tables with no header cells.&lt;/p&gt;

&lt;p&gt;Every finding carries the WCAG 2.2 success criterion it maps to, the number of instances, the offending elements with their position in the document, and a copy-paste fix. The checks are deterministic code: the same HTML always produces the same findings, nothing is model-generated.&lt;/p&gt;

&lt;p&gt;The free scan shows the top five finding groups. The full report — one-off $29 per site, no subscription, no account — shows every finding with up to five examples each, grouped by severity, plus a coverage table (each check that ran, how many elements it examined, and whether it found failures) and a 13-item manual-review checklist. Your agency's name goes at the top as "Prepared by …", and the report prints or saves to PDF from the browser. Nothing is emailed anywhere; the file you save is the deliverable. Nothing about the visitor is stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it cannot see
&lt;/h2&gt;

&lt;p&gt;No JavaScript runs. There is no browser — the page is fetched and parsed as HTML — so anything rendered on the client (React, Vue, cookie walls, JS-built menus) is invisible, and a client-rendered page shows fewer findings here than it actually has. Document-level checks still work (title, &lt;code&gt;lang&lt;/code&gt;, viewport meta, iframe titles), because those arrive in the server HTML.&lt;/p&gt;

&lt;p&gt;The checks can't judge quality. They can prove an &lt;code&gt;alt&lt;/code&gt; attribute exists; they can't tell you whether it describes the image usefully. Same for link text and error messages. Contrast, keyboard operation, focus order, keyboard traps, screen-reader announcements and timing all need a person, as does anything behind a login or inside a third-party widget. And one page is a snapshot, not a crawl: you get the address you pasted, as it was a moment ago, not the rest of the site and not a history.&lt;/p&gt;

&lt;p&gt;The sentence on the site, verbatim: "Automated tools catch only a minority of WCAG issues. AuditReady is not a compliance guarantee and not legal advice — a manual review by a person is still required." If your stack renders on the client, use axe or Lighthouse with JS rendering. I'd rather say that than take $29 for a thin result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not an overlay, why not a crawler
&lt;/h2&gt;

&lt;p&gt;An overlay injects a script into the site and promises to make it accessible. That's a claim no automated scan can support, and it's the one thing the accessibility community has spent years arguing against. AuditReady doesn't touch the scanned page at all: no snippet, no script, no change its visitors ever see. A crawler is the other tempting direction, and whole-site monitoring is a real need — but it's a different product. If you want coverage over time, or proof that forty things got fixed last month, use a monitoring platform. This is for the page you're about to ship or hand over.&lt;/p&gt;

&lt;p&gt;Because the scanner fetches arbitrary URLs, the fetcher is guarded: the hostname is resolved before any connection and non-public addresses are refused, redirects are re-checked on every hop, and there's a 15-second deadline with a 3 MB cap enforced while streaming.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real example
&lt;/h2&gt;

&lt;p&gt;There's a live report for python.org — produced by an actual scan of that page and served from the capture, findings untidied: &lt;a href="https://feaefe371424d0725b266468856c9101.ctonew.app/report?domain=https%3A%2F%2Fwww.python.org%2F&amp;amp;agency=Northline%20Studio" rel="noopener noreferrer"&gt;a real AuditReady report for python.org&lt;/a&gt;. The agency name on it, Northline Studio, is explicitly labelled a placeholder that implies no client relationship. Re-scan python.org yourself and compare.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tell me what the checks miss
&lt;/h2&gt;

&lt;p&gt;If you have five minutes, run a page you know well through the free scan and tell me what's wrong with it. The false negatives are already listed above; the interesting ones are the false positives, and the checks that should exist and don't. Comments here are the best place — the scan is free, rate-limited to 5 scans per rolling minute per IP, and nothing about you is kept.&lt;/p&gt;

</description>
      <category>a11y</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
