Accessibility
SchoolScope targets WCAG 2.2 Level AA. This page states what we measured, what we have not measured, and where each falls today — a checkable claim, not a badge.
The standard
We target the Web Content Accessibility Guidelines (WCAG) 2.2, Level AA. Below is what we have actually measured against that standard, and what remains unmeasured — deliberately kept separate, because a promise and a measurement are not the same claim.
Accessibility is a practice, not a checkbox. Some pages are ahead of others at any given moment, and this page says so rather than claim uniform coverage.
What we measured
The seven rows below are a browser measurement taken on the date above and checked into this page by hand — nothing re-runs them automatically, and a token change elsewhere in the codebase would not be caught until someone re-measures. The control-boundary row beneath them is different in kind, not just in number: it runs as part of every deploy and fails the build if a boundary drops under 3:1, so that one figure stays current on its own.
Color contrast, painted as it actually renders in the browser (not parsed from a token declaration — an oklch() value is measured post-render), across both light and dark scheme, for the token pairs that carry running prose and data:
| What it is | Light | Dark |
|---|---|---|
| Body prose on the page ground | 6.11:1 | 7.49:1 |
| Headings and bold text | 14.04:1 | 16.60:1 |
| Timestamps and captions | 4.78:1 | 6.17:1 |
| Prose links | 9.21:1 | 9.43:1 |
| Body prose on a card or sheet | 7.10:1 | 6.46:1 |
| Typed microlabels on a card or sheet | 5.55:1 | 5.32:1 |
| Table cells | 16.30:1 | 14.32:1 |
| Control boundaries, floor 3:1, SC 1.4.11 | 0 of 40 under 3:1 | 0 of 40 under 3:1 |
80 pairs checked across both schemes (40 light, 40 dark, shown as 0 of 40 under 3:1 per scheme above); 6 (3 selectors × both schemes) could not be resolved to a fixed color and are not counted as measured either way — two selected-state controls layered over a tinted ground, plus one button whose ground is a CSS named color the parser does not resolve, and all three are interactive components. Two different things determine what that 40-per-scheme figure covers: which files get read, and which rules in those files can be measured once read. The pass reads every file under src/routes and src/components in full, plus the design system file itself, the blog prose stylesheet, and the inline-assets build script — but a border written as a Tailwind arbitrary-value class straight into a JSX class attribute (border-[var(--control-edge)]) carries no parseable CSS rule, so it is read but never counted: the search box on our 404 page and the one on the compare page are both in that bucket, along with every link chip and pill on the area, city, ZIP, and district pages, plus a handful of buttons and a text field on /admin and /profile. The source declares this class at multiple call sites, several of them inside a loop that renders one chip per district, city, or ZIP shown — so a single area page can render far more unmeasured boundaries on its own than there are declaring lines in the source, and any fixed count printed here would go stale the next time a call site is added or removed. src/islands and src/lib are not read at all — the passkey-management dialog's input field and Cancel button (an island) and the cookie banner's Decline button (src/lib/consent.ts) are the concrete examples; none of the three is counted as measured either way. It is not every interactive element on the site; see the next section for what still falls outside it.
What that check cannot see
Eight things a static contrast pass does not cover, named rather than implied:
- Keyboard access, focus indicators, text alternatives, and reduced-motion support are built into the design system (every interactive element takes keyboard focus, images carry alt text, animation respects
prefers-reduced-motion) but none of it is run through an automated audit the way contrast is. These are a design commitment, not a measured claim. - The full cross-product of every text token against every ground is a report-only pass, not a conformance claim — most of the pairs it checks never actually render together on a page.
- A border or background set with a Tailwind arbitrary-value class (
border-[var(--control-edge)]written straight into a JSXclassattribute) carries no parseable CSS rule at all — it is a structural blind spot, not a file the pass forgot to open. The source declares this class at multiple call sites, several of them loops that render one chip per district, city, or ZIP shown, so the number actually rendered is larger than the number of declaring lines in the source — including the search inputs on our 404 page and the compare page, the link chips on area pages, and a handful of buttons and a text field on/adminand/profile. - CSS declared in
src/islandsorsrc/libis not read by the control-boundary pass at all, regardless of whether it's a stylesheet rule or something else — the passkey-management dialog's input field and Cancel button (an island) and the cookie banner's Decline button (src/lib/consent.ts) are the concrete examples. - CSS written as an inline HTML
style="…"attribute string, rather than a stylesheet rule block — a handful of call sites that build widget markup as raw HTML strings — is invisible to the rule-block parser both passes use, and is hand-verified instead of measured automatically. - Anything painted by client-side JavaScript after the page loads — map pins, the search results dropdown, the explore results rail — is outside both passes.
- Reflow at narrow widths (WCAG 2.2 SC 1.4.10) is not measured site-wide. Two known failures are on record at 320px CSS width: the developer page's code-language tab row runs past the viewport edge, and the contact form overflows because the verification widget reserves a fixed-width box wider than the form card's padded interior. Both are open; this list updates as they close.
- No assistive-technology testing is claimed. Contrast is measured. Screen-reader behavior on dynamic components has not been tested with real assistive technology, and nothing on this page should be read as implying it has.
Known limitations
Maps. The interactive school map is built on Apple MapKit, which exposes only a light and a dark color scheme — the basemap itself cannot be restyled by us. Map pins carry a color cue for a school's band; every pin also carries a non-color signal (the score or a shape), because color alone is never the only indicator. We provide the same school data in accessible table form on the explore page, which is the recommended path for screen-reader users.
Data-dense tables. Some school comparison and appendix tables sit inside a horizontally-scrolling container on narrow screens. In practice that means touch drag on a phone, and a visible scrollbar where the platform draws one at rest (Windows/Linux; macOS and iOS hide it until the container is scrolled). Firefox and Chromium make a scroll container keyboard-focusable when it holds no focusable children; WebKit does not — so keyboard access to that scroll is not universal across browsers, and we do not claim it site-wide. The same data is available on individual school profiles. This is different from a page itself scrolling sideways, which is the reflow gap named above.
Found a barrier?
If any part of SchoolScope is hard or impossible for you to use, tell us — that's a bug, and we treat it like one. Contact us with the subject "Accessibility" and describe what you were trying to do and what got in the way. We aim to respond within 10 business days.