A monthly automated accessibility report covering 112 University of California web properties — campus homepages, admissions sites, and schools and colleges across all 10 UC campuses and the Office of the President. Inspired by the WebAIM Million project.
Live report: https://rlorenzo.github.io/uc-homepage-a11y-report/
On the first of every month a GitHub Actions workflow loads each UC web property in headless Chromium, runs axe-core against it, and records the results. The report site is rebuilt and deployed to GitHub Pages so that month-over-month trends are visible at a glance.
Violations are bucketed into two groups:
- Required — WCAG 2.0 and 2.1 Level A & AA rules. The baseline under ADA Title II and Section 508.
- Reach — WCAG 2.1 AAA and all of WCAG 2.2. Tracked separately as aspirational goals, not yet legally required in the US.
The report covers 112 UC web properties:
- 11 campus homepages — one per UC campus and the Office of the President.
- 10 admissions sites — one per campus. UCOP has no admissions page; UCSF uses its Graduate Division admissions page.
- 91 schools, colleges, and graduate divisions — grouped by discipline (business, engineering, law, medicine & health, humanities & arts, social sciences, natural sciences, education, environment & design, information & journalism).
The full list with URLs lives in scan/sites.json. Use
the filter bar at the top of the live report to view any slice (by campus,
by type, or by discipline).
Requires Node.js 24 or later.
npm ci
npx playwright install chromium
node scan/run.mjsA full scan takes 10 to 12 minutes on a fast connection. Results are written
to data/runs/YYYY-MM/<slug>.json (one file per site, full axe output) and
appended to data/history.json (compact per-month summaries that the report
site reads).
To view the report, serve the site/ directory with any static server:
npx serve siteThe report loads data/history.json at runtime. The repo ships with a
site/data symlink to ../data, so most static servers resolve it
automatically.
For local iteration you rarely want to re-run all 112 sites. The scanner accepts filter flags that combine with AND:
# Just UCLA sites
node scan/run.mjs --campus=ucla
# All 11 homepages
node scan/run.mjs --type=homepage
# Specific sites by slug
node scan/run.mjs --slug=berkeley-haas,ucla-anderson
# All admissions sites on Berkeley and UCLA
node scan/run.mjs --type=admissions --campus=berkeley,ucla--type accepts homepage, admissions, school, or division — these
match the raw type field in scan/sites.json, not the plural chip labels
in the report UI. All three flags accept comma-separated lists.
Filtered runs write into the same data/runs/YYYY-MM/ folder and replace
only the matching rows in history.json — other rows for that month are
left untouched.
Edit scan/sites.json. Each entry needs:
| Field | Description |
|---|---|
slug |
Short identifier used in file names and history keys. Must be unique. |
name |
Display name shown in the report. |
campus |
One of berkeley, ucla, ucsd, ucdavis, uci, ucsb, ucsc, ucr, ucmerced, ucsf, ucop. |
url |
The page to scan. |
type |
homepage, admissions, school, or division. Controls which View chip the site appears under. |
category |
homepage, admissions, or a discipline slug (business, engineering, law, medicine-health, humanities-arts, social-sciences, natural-sciences, education, environment-design, information). Controls the Discipline dropdown. |
No code changes required — new entries are picked up on the next scan.
The GitHub Actions workflow (.github/workflows/monthly-scan.yml) is triggered
by a cron schedule (0 14 1 * *, 14:00 UTC on the 1st) and also supports
manual dispatch via workflow_dispatch. It:
- Checks out
mainat runtime (not the trigger SHA — protects against stale reruns). - Installs Node.js 24, npm dependencies, and Chromium.
- Runs
node scan/run.mjsto scan all sites. - Commits any new data under
data/with the message "Monthly scan YYYY-MM", rebasing ontoorigin/mainfirst to handle concurrent pushes. - Deploys the
site/directory — withdata/history.jsoncopied intosite/data/— to GitHub Pages.
If the workflow is manually re-triggered for a month that has already been scanned, the existing rows for that (month, site) pair are replaced rather than duplicated.
If a site's numbers change between consecutive scans and you want to know whether it's a real site change or a scan-method artifact, run:
npm run verifyThis scans every site twice — once with the old desktop-only viewport and
once with the mobile → desktop transition the production scanner uses — and
prints a per-site diff. It does not write to data/.
Automated accessibility scanning with axe-core typically detects only 30 to 40 percent of real accessibility issues. This report provides a useful lower bound, but it is not a substitute for manual testing, assistive technology evaluation, or testing with users who have disabilities.
MIT