Skip to main content
Real web · Before and after · Re‑verified live

Measured on the real web, not on fixtures.

From 2026-09-22 to 2026-09-23 we put the scanner through a controlled audit: 58 real websites, 5 pages each, scanned through production before and after a round of accuracy fixes. Then we went back to every finding the fixes removed and checked it on the live page. This is what we found, including the parts that did not flatter us.

Findings

860

from 823 · +4%

Duplicate rows

0

from 14 · the same defect, reported once

Findings with no rule id

0

from 0 · every row is filterable

September 2026 field audit IV · 51 of 58 sites comparable (3 blocked the scanner in both runs, 4 changed reachability between runs) · builds 5364104 → 2d89faa · ran 2026-09-22 to 2026-09-23

Engine by engine

Rule-class counts are a poor scoreboard for an accuracy fix, because a fix that gives a row an id it previously lacked moves that row between classes without removing anything. Engine totals cannot be moved by relabelling.

SourceBeforeAfterChange
arc-style7983+4
axe-core177185+8
ibm-equal-access199165−34
lighthouse1916−3
pa11y127111−16
In-house analyzers, reported through the same field
focus-visibility078+78
keyboard-nav-tester31−2
language-check10−1
mobile-accessibility218197−21
reflow-320024+24

Did the fixes remove anything real?

Fewer findings proves nothing on its own. Every occurrence present in the first run and absent from the second - 2,583 of them - was re-measured on the live page with a method independent of the scanner: a verifier that shares no code with the scanner. Names, roles and hidden-ness come from Chromium's own accessibility tree over the DevTools protocol; contrast from rendered pixels, the glyphs isolated by screenshotting the text and then the same box with the text made transparent; id references by token lookup in the element's own root; focusability by calling focus() and reading the active element. Each occurrence keeps the scanner's page preparation (1920x1080, light theme, reduced motion, the scroll primer). The before run is the field sweep of 22-23 September on 5364104; the after run rescanned every sweep site on 2d89faa, the build with PRs #47 to #50. Every occurrence the comparison counted has a row here: target-size occurrences, which the verifier hands to a separate mobile pass, are counted as indeterminate rather than dropped. The bias runs toward reporting a lost true positive, not toward excusing the removal.

2,583 removed occurrences
Correctly removed87
Still counted, just not listed37
Same element, listed under another row375
Re-bucketed under another rule4
Genuinely lost30
Removed by a documented policy16
Indeterminate (dynamic pages)includes 18 not explained2,034
1,764 newly reported AA failures
Justified - the verifier confirms the failure on the live page523
Borderline - at exactly the 24px threshold0
Manufactured by the fixes22
Indeterminate (dynamic pages)1,219

The 30 genuinely lost occurrences are on ten sites: syntax-highlighted code tokens on ionicframework.com (8) and angular.dev's editor (3) and a primary button on shoelace.style (3), all contrast; five labelledby references and one contrast row on vimeo.com; the skip link and main landmark of one detik.com page (3); two labelledby references on gymshark.com; two unnamed icons on jotform.com; an OneTrust reference and a heading level on khanacademy.org; a heading level on naver.com; and a language button on yle.fi whose visible text is not in its name. Each is filed as an open defect against the after-build. The 18 unexplained rows are occurrences the reconciliation could not resolve on the live page hours later and are shown inside Indeterminate; they are not evidence either way. The indeterminate rows are pages the other run did not analyse, target-size occurrences (judged by a separate mobile pass this census does not fold in), dynamic content whose selectors no longer resolve, and rows inside consent or ad vendors' frames.

Removed by a documented policy, 16 occurrences. 16 occurrences were removed by decisions the field sweep verified by hand, where this verifier keeps the literal reading: a tabindex on a scrolling code block needs no widget role (docusaurus.io, ionicframework.com, 6); an empty, zero-height listbox is not a nameless control (canada.ca, 2); inactive pager links are exempt from 1.4.3 (hurriyet.com.tr, 2); text hidden by image replacement is never painted (gov.uk); an icon-font ligature is not a visible label (angular.dev); an icon inside a named role=img needs no name of its own (shoelace.style); and a card link named by the call to action it shows meets 2.5.3 (vimeo.com, 3). They are neither counted as correctly removed nor as lost.

Where the removals came from

Only the classes the fixes touched. Everything else moved with the web, not with the scanner, and is not attributed.

Rule classBeforeAfterWhat changed
mobile-target-unreachable-advisory4630opacity, axis-aware clipping, whole scroller chain
ibm-fieldset_label_valid70named fieldsets and single-field wrappers
ibm-aria_attribute_valid137prohibited ARIA that browsers ignore at no cost; aria-checked on native inputs
ibm-label_content_exists72label text, title or a named control
link-name83icon-font glyphs are not names; accessibility-tree fallback
mobile-target-size-aa-fail116inline exception; spacing ignores fixed layers and other scroll boxes
ibm-text_contrast_sufficient2924backgrounds not behind the text; fixed overlays
ibm-aria_eventhandler_role_valid52redundant mouse targets, empty handlers, shadow-rendered buttons
input-label-missing31an empty label named by its title; names from the accessibility tree
ibm-svg_graphics_labelled2725empty, unrendered or img-hosted svgs
ibm-aria_hidden_nontabbable86hidden content nothing inside can focus
ibm-element_tabbable_role_valid2119tabbable scroll containers
keyboard-trap31closed shadow roots and documented Escape exits
ibm-aria_role_valid54role=none on li is permitted
label-title-only32judged by the name's real source
label-content-name-mismatch1211icon-font text; a card named by its call to action
blink10an empty blink has nothing to blink
aria-role-invalid33role="text" is a low note, not an invalid role
ibm-aria_role_allowed33role="text" is a low note
aria-prohibited-attr11prohibited ARIA that browsers ignore at no cost
ibm-aria_descendant_valid22an image flattened inside a named button is a note
ibm-input_label_exists33collapsed listbox; names from the accessibility tree
aria-hidden-focus55hidden content nothing inside can focus
page-has-heading-one1010checked against the live page, shadow roots included
ibm-label_name_visible77a card named by its call to action
target-size17172.5.8 inline and spacing exceptions
link-in-text-block44a link 3:1 from its sentence
color-contrast1616CSS filters, image-replaced text, inactive components, brand names
tabindex11best-practice rules cap at medium
button-name78names read from Chromium's accessibility tree
duplicate-id910labelledby copies that render the same text
reflow-horizontal-scroll024new: 1.4.10 measured at 320 CSS px on every page
focus-indicator-invisible078new: 2.4.7 measured in pixels on the first two pages

Every site

Scores moved on 38 of 51 sites: 22 up, 16 down. A score can fall because the report got more accurate - when two engines' reports of one defect merge into a single row, the row keeps the higher severity.

SiteFindings beforeAfterScore
apnews.com271751 → 56
gov.hk10484 → 89
mozilla.org12777 → 79
techcrunch.com282354 → 55
yle.fi191480 → 76
khanacademy.org11776 → 89
ionicframework.com312851 → 52
svt.se7489 → 100
ikea.com121084 → 83
npr.org292761 → 65
a11yproject.com7687 → 89
airbnb.com171665 → 65
canada.ca7689 → 100
gov.uk4389 → 100
developer.mozilla.org121183 → 83
shoelace.style161570 → 72
svelte.dev111079 → 89
vuejs.org171659 → 59
deque.com7789 → 89
docusaurus.io141479 → 75
design-system.service.gov.uk33100 → 100
naver.com292959 → 59
nextjs.org272774 → 70
rust-lang.org6687 → 87
squarespace.com8861 → 80
typeform.com181875 → 67
webaim.org5587 → 88
allbirds.com151661 → 64
bbc.com/hindi7853 → 89
bbc.com/persian6759 → 89
monzo.com161775 → 78
notion.com171864 → 53
rnib.org.uk4577 → 79
ted.com252667 → 53
wordpress.org56100 → 89
astro.build6889 → 87
duolingo.com222449 → 49
go.dev171960 → 55
gymshark.com384050 → 50
mit.edu121478 → 78
angular.dev242768 → 60
theguardian.com232754 → 63
shopify.com131879 → 76
whitehouse.gov111687 → 83
g1.globo.com243053 → 54
detik.com435050 → 44
service-public.fr18100 → 79
hurriyet.com.tr253351 → 51
gov.br192870 → 67
jotform.com243349 → 49
vimeo.com324149 → 49

What this page does not claim

  • Not "4% more real defects found." Findings rose by that much. A total moving in either direction mixes removed false positives, checks that were switched on between the runs and changes on the sites themselves; the census above is the only honest breakdown we have.
  • Not a false-positive rate. Producing one requires re-verifying every finding that remains, on the live page. We re-verified what changed. A single percentage would be more quotable and less true.
  • Not deterministic. Live sites ship new markup, rotate carousels and block scanners between runs. 3 sites were unreachable in both runs and are excluded; a site that changed reachability between runs is excluded too.
  • Not the whole story. 84 defects were found and fixed in this audit, each with a regression test proven to fail without it. Some were in the fixes themselves, found by the census. The open items are listed in the repository.

Reproduce it

  • Every number on this page is computed from the audit's raw result files by scripts/build-proof.py and committed with the site; none is typed by hand.
  • The fixture benchmark is the other half of the evidence: precision and recall on hand-annotated ground truth.
  • The scanner audited here runs on every pull request through the accessibility GitHub Action.
  • 5,126 backend tests pass in both CI modes on the build that shipped these fixes.
  • The raw figures for this edition: 2026-09-23.json. Other editions are listed in the field audit series.