Accessibility health scoring

A plain-language guide to how the scan report health gauge score is calculated, what it means, and why it sometimes disagrees with the violation count.

Accessibility health scoring

Sometimes a scan report shows a site with hundreds of violations but a score that still looks high, and that looks like a contradiction. This guide explains how Cascadia Marquee arrives at the accessibility health score, the number in the middle of the gauge on every scan report.

It is written for anyone who looks at a scan report, whether that is your own site's report or one you are helping someone else read. You do not need to be a developer to follow it. By the end, the score should make sense to you, including the cases that feel backwards at first.

What the score is

The health score is a number from 0 to 100 that answers one specific question:

Across the pages we sampled, how healthy is each page on automated checks, and what is the average of those page scores?

In short, higher is better. A higher number means fewer weighted barriers per page on average.

It is just as useful to know what the score is not:

  • It is not "violations per page" alone. Severity and how often a problem repeats on a page both matter.
  • It is not a count of every element on the site.
  • It is not a measure of whether we scanned the whole site. We only sample pages.
  • It is not a WCAG compliance grade, and it is not a legal or ADA pass/fail.

One more caveat, which the gauge itself shows on finished reports: automated tools cannot catch everything. A human tester reviewing the site can still find issues that no automated scan will ever see. The score reflects the automated surface, not the full accessibility picture.

For the technically curious: if you have access to the codebase, the math lives in src/lib/accessibility-scan-analysis.ts, in computePageHealthScore and computeHealthIndex. The report UI runs analyzeScanFindings when a scan finishes. See scan-report-view.tsx and accessibility-portal-data.ts. You can skip this note if code is not your thing.

The three numbers people mix up

Most confusion about scores comes from treating three different numbers as if they were the same thing. They are not. Each one is different:

MetricWhat it is really countingWhere it comes from
Health scoreAverage of per-page deduction scores (severity x on-page pervasiveness)Calculated when the report renders, from each page's axeViolations
totalViolationsThe number of failing rules, counted once per pageapps/scan-worker/src/scan.ts sets violationsCount = axeViolations.length per page; queue.ts adds those up
Flagged elementsThe actual DOM nodes (buttons, links, images, and so on) that a rule flaggedaggregateFindings in accessibility-scan-analysis.ts

Why a weak page pulls the site score down

Imagine a collection grid with dozens of unlabeled product links. That page gets a low per-page score because the same serious rule repeats many times. When we average page scores for the site, that weak page (and others like it) pulls the headline down. Clean content pages do not hide a broken homepage or product grid.

How the score is calculated, step by step

Step 1: Score each page with a deduction model

Every scanned page starts at 100. For each failing axe rule on that page we subtract a penalty based on:

ImpactWeight
critical10
serious7
moderate3
minor1

We also amplify the penalty when the same rule flags many elements on that page (sublinear "pervasiveness," so 30 unlabeled links hurt more than one without zeroing the page). Passing checks do not dilute the page score.

pageScore = clamp(round(100 - DEDUCTION_SCALE × sum(impactWeight × pervasiveness)), floor, 100)

Step 2: Site score = average of page scores

siteScore = round(mean(pageScores))

This matches the pattern used by tools like accessibleweb RAMP: a site is only as healthy as the average of its pages.

Step 3: A few final adjustments

  1. Never show a perfect 100 if anything is still broken. If there is any failing weight left at all, the score is capped at 99.
  2. No accessibility statement means subtract 8 points. If the scan did not find a dedicated accessibility statement page (statementFound === false), we take 8 points off.
  3. Keep it in bounds. Finally, clamp the result to the 0 to 100 range.

How the individual ring scores work

Each of the six categories on the gauge still gets its own score for the ring breakdown (pass-ratio or density fallback, limited to that category). The center number is the per-page average above, not the mean of the rings.

There is also a seventh segment, Accessibility statement, scored on its own simple scale: 100 if a statement was found, 35 if it definitely was not, and 70 if we could not tell. This segment affects the center score only through the 8-point adjustment above.

What the bands mean

Once there is a final number, it lands in one of five bands. This is what drives the label and color you see on the report:

ScoreBandUI label
95-100ExcellentExcellent
90-94GoodGood
80-89FairFair
70-79NeedsWorkNeeds work
0-69PoorPoor

Third-party embeds

Some findings come from apps and embeds (Yotpo, YouTube, Instagram, and similar) rather than your theme files. We detect those from the element's selector and HTML, tag them as third-party, and point fix guidance at the app instead of sections/header.liquid. You can hide third-party findings in the report and optionally view a score that excludes them as an estimate.

Two situations that look backwards (and why they aren't)

A site scores in the 80s with hundreds of violations

This is normal when many pages each have a modest number of serious issues. The page scores sit in the 70s and 80s, and the site average follows. The violation count can still look large because the same rules appear on many URLs.

A homepage scores much lower than content pages

Also expected. Global navigation and shared widgets often concentrate barriers on the homepage and collection templates. Content pages with fewer interactive patterns score higher. Averaging pages makes that tension visible in the headline score.

A common gotcha: whenever a score looks "too high" or "too low" for the violation badge, check whether the site has an accessibility statement and whether many findings are third-party embeds. The 8-point statement adjustment and third-party filter explain a surprising number of off-looking scores.