Skip to content

ACCESSIBILITY

What was measured, what it measured at, and the one thing that fails today.

This site is ten prerendered pages in English, nine more in Spanish under /es and nine more in Brazilian Portuguese under /pt-br, one form, four diagrams, three films, one scannable code, and a 404 the server renders on demand in whichever of the three languages was asked for. Every count below is a count of the English documents, because a landmark is markup a reader receives rather than a component that emits it, and the translated pages are held to the English shape by a test instead of counted again here. Everything below was measured against that build — the colour tokens as they resolve inside each section, the focus ring as it is actually drawn, the form as it behaves with the script off, the films as they are actually served — rather than adapted from a template. Where a figure appears here, it is the figure the measurement returned.

The target is WCAG 2.2 Level AA, and the site does not meet it. Three films carry narration and none of them carries captions, which fails a Level A criterion outright. That is stated here first because it is the most likely thing on this site to shut somebody out, and the rest of the page is of no use to a person it has already stopped.

LAST UPDATED · 2026-10-13

THE OPEN FAILURE

Three films have narration and no captions.

Three files in four places: a thirty-second film on the home page and the same file again on the request-access page, a sixty-three-second one on the overview page at /overview, and a fifty-second one on the demo page. All three have an audio track carrying speech, and none of them ships a captions file.

This fails WCAG 2.2 success criterion 1.2.2, Captions for prerecorded media, at Level A — the lowest bar the standard sets. It is not a near miss or a judgement call. A person who is deaf or hard of hearing gets the picture and loses whatever the narration said, and on the demo film the narration is most of the point.

What the films do have is not a substitute, and is listed here so nobody mistakes it for one: each sits in a figure with a visible caption beneath it, each carries an aria-label, each is served with preload set to none behind a poster frame, and none of them autoplays, loops or hides its controls. A person who can hear is not ambushed and a person on a metered connection is not charged for a film they did not ask to watch. Neither of those helps the person this criterion exists for.

  • Measured — three files, 30, 50 and 63 seconds, every one with a speech-bearing audio track at a mean of roughly -23 dB.
  • Missing — a track element with captions on each film, and a transcript beside it.

SCOPE

This statement covers the site, and not the other surfaces.

It covers all ten English pages of www.distcorpus.com, the nine Spanish pages served under /es, the nine Brazilian Portuguese pages served under /pt-br, and the request-access form, which is one component rendered on three pages of each language. Corpus itself is reached through an assistant the district already runs, and that assistant's window is not ours to make accessible.

The connector at mcp.distcorpus.com/mcp has no interface at all. What a person sees when they query Corpus is Claude's, ChatGPT's, Copilot's or Gemini's own window, built by someone else, and nothing on this page claims anything about it.

Two surfaces of ours are deliberately outside this statement: the citation viewer at cite.distcorpus.com, which opens a record on the page a figure was printed on, and the district dashboard. Both are separate deployments, neither has been measured the way this site has, and extending this statement to cover them before that happens would be exactly the kind of claim this page exists to avoid.

One page of this host does not get that exemption and is easy to miss, so it is named here. /qr is a full-screen scannable code made for handing out at a conference: it is noindexed, nothing on the site links to it, and it can also be installed to a phone's home screen. It is prerendered by the same build command onto the same host as the other nine, so it is in scope and measured below rather than waved off with the two surfaces above. It renders through a layout that deliberately carries no site chrome, so the code fills the screen, and that choice has a price this page will not bury: it has no skip link, its main landmark carries no tabindex, and its header sits inside that main, which makes the header a generic element rather than a banner. The page offers two landmarks, its main and the one named region described next, where every other page carries four before any of its own regions are counted, and a reader who lands on it by scanning has no navigation and no contentinfo to orient by.

What it does carry was measured the same way as the rest. It declares lang="en", carries exactly one h1 — visually hidden, naming the code rather than repeating the page title — and carries one section landmark, tied by aria-labelledby to the label above its demo-prompt field rather than to a heading, which is the one such tie on the site. The code itself is an SVG with role="img" named by its own title element, and the same address sits under it as an ordinary link, so nothing on the page depends on a camera or on seeing the code at all. Nothing on it conveys meaning by colour alone: the copy confirmation is words in a polite live region rather than a colour change, and every text colour it paints is a paper or paper-alt token already counted under contrast above. The one control the prerendered markup does not contain is the install button, which a browser offering an install renders at runtime as a button named Add to Home Screen, and on iOS as a disclosure that sets aria-expanded and aria-controls on the instruction it opens.

  • In scope — the ten English pages of www.distcorpus.com — the nine the sitemap lists in English and the unlisted /qr — the nine Spanish and nine Brazilian Portuguese pages it lists beside them, the one form, which three pages of each language render, and the three films.
  • Out of scope — the assistant's own window, the citation viewer, and the district dashboard.

CONTRAST

Every text colour clears 4.5 to 1, and the narrowest margin is 4.68.

The site paints five section fields — paper, paper-alt, sand, ink and accent — and each one redefines its heading, body and muted text against its own background. Those pairs were computed from the tokens rather than eyeballed on screen.

The narrowest pair on the site is muted text on the indigo accent field, at 4.68 to 1. The accent field is the tightest ground on the site in general: its white body text sits at 5.37 to 1, where body text on the paper field has 9.30 to 1 to spare. Nothing there fails, and nothing there has much room, so a later darkening of the indigo is the change most likely to push a pair under the bar without anyone noticing.

Three fields mix their muted token towards their own ground so the same role stays legible on a different background, and on the current palette that mixing is what buys the margin rather than what rescues a failure: muted resolves to 6.55 to 1 on sand and 7.51 to 1 on ink, where the unmixed token would have landed on 4.50 to 1 on sand — passing, but by a hundredth. The third mix belongs to the accent field, and it is the one that produces the 4.68 above.

Focus rings are held to a separate standard, 3 to 1 for a non-text indicator, and clear it with room: the narrowest of the five is the indigo ring on the sand field at 4.80 to 1. The four diagrams are the other place colour could have carried meaning alone, and none of them does — each is an SVG with role="img" named by its own title element, so a reader who cannot see the drawing is told what it shows instead of hearing nothing.

  • Narrowest text pair — muted on accent, 4.68 to 1, against a bar of 4.5.
  • Narrowest focus ring — accent on sand, 4.80 to 1, against a bar of 3.

KEYBOARD AND FOCUS

Everything is reachable with a keyboard, and the focus ring is never suppressed.

There is no CSS anywhere in the site that removes an outline, and no custom focus management beyond five deliberate moves.

A skip link is the first focusable thing on every page that carries the site chrome, which is nine of the ten plus the 404. It is hidden until focused and jumps to the main landmark, which carries tabindex -1 so the jump actually lands somewhere rather than being ignored. /qr is the exception on both counts — no skip link, and a main with no tabindex — for the reason given under scope above. The ring itself is a two-pixel outline at a two-pixel offset, drawn on :focus-visible so it appears for a keyboard and not for a mouse.

The five moves, and there are no others: the skip link into main; Escape closing the mobile menu and returning focus to the button that opened it; Escape closing the citation panel on the overview page and returning focus to the figure that opened that, which is a second Escape listener rather than a shared handler; a submission that fails validation sending focus to the first field that was refused; a submission that succeeds sending focus to the message that replaces the form.

  • Disclosures are labelled, unevenly — the menu button sets aria-expanded and aria-controls, takes its label from a visually hidden span and marks its icon aria-hidden. The citation panel's trigger sets both ARIA attributes but is named by its visible text, and the button closing it is the other way round — a hidden-span label and an aria-hidden icon, with no ARIA state at all.
  • No focus trap — the mobile menu is a disclosure rather than a dialog, so Tab leaves it and Escape closes it.

STRUCTURE

One h1 per page, and all thirty-two section landmarks name themselves.

For a screen reader the document outline is the navigation, so it is stated exactly here rather than described as good.

Every page declares the language it is written in, lang="en" on the English pages, lang="es" on the Spanish ones and lang="pt-BR" on the Brazilian Portuguese ones, and carries exactly one h1. On the nine English pages that carry the site chrome, on their nine Spanish and nine Brazilian Portuguese counterparts, and on the 404 with them, that h1 is the heading the page opens on, and below it sit one banner with a navigation labelled Primary, one main landmark and one contentinfo footer; /qr carries the main without the other three. The type scale is separate from the heading level — a heading is sized by a utility class rather than by being demoted to the size that looked right — so nothing skips a level to get smaller.

Counted in the markup a reader actually receives rather than in the components that emit it, the ten English pages and the English 404 carry thirty-two section landmarks, and every one of them names itself. Thirty are tied by aria-labelledby to their own heading, one — the demo-prompt block on /qr — to the label above its field, and one — the use-case carousel on the home page — carries an aria-label instead. A reader listing the landmarks of any page on this site is told what each one is, and there is no nameless section left anywhere on the site.

That every one of them names itself is new, and what the change responsible actually bought is worth stating exactly, because the figure this page carried before overstated it. This paragraph and the next are a record of that fix rather than a second description of the export above: every count in them was taken when the fix landed, on an export since re-cut into the pages listed above, and thirty-two is the total today. The count went from forty-four section elements to thirty-nine. Seventeen of the forty-four had nothing to name them, and a section with no accessible name is not a landmark at all: ARIA in HTML gives it no role beyond generic, which is what Chromium's accessibility tree shows — no name, no region, and no entry in any landmark list. So those seventeen were never offered to a reader as landmarks, and this page was wrong to say they announced themselves as a bare section.

The cause was still one component, and the fix had two halves. The section component emitted a section element whether or not it had been given anything to name it; it now emits a section only when it has an accessible name, and an ordinary div otherwise, carrying the same id, the same field tokens and the same classes, so every in-page anchor still lands where it did. That half tidies the tree rather than the landmark list, and the gain is small enough to be named as small: a div is pruned from the accessibility tree outright, where a nameless section is kept as a generic node a reader walks through for nothing. The half that matters is the other one. Ten bands that had a heading but no aria-labelledby were tied to it — the h1 openings of the request-access, trust, demo and how-it-works pages, the 404, and the one component that opens all three legal documents, plus four on the home one-pager — which is twelve section elements in the export, because that one component renders three times. Named regions went from twenty-seven to thirty-nine on that export, and those twelve are landmarks a reader can list that were not there before.

Six pages carry two languages at once, and that is a decision rather than a lapse. /privacy, /terms and /accessibility are served inside each language's tree and render the same English legal body, which is the operative text, so each Spanish and each Brazilian Portuguese edition marks that body lang="en" inside a document declaring its own language and opens it with the notice saying which text binds, written in the page's language and marked with it. That is WCAG 2.2 success criterion 3.1.2, Language of Parts, and those six pages are the only place on the site where a passage is in a language its page is not.

Five bands on the site are now divs, and that is the intended answer rather than a remaining gap: the body block of each of the three legal documents, the values band of the home page, and the film band on /overview. None of them has a heading, and none was given an invented one — a hidden heading written only so a landmark had something to point at, or an aria-label composed to paper over the absence, would have put a name in the outline that nothing on the page says. A band with nothing to call itself is better as a plain container than as a generic node the tree keeps and a reader gets nothing from.

  • Landmarks — banner, navigation, main and contentinfo, one of each on the nine chrome pages of each language and on the 404; on /qr, the main alone of those four, with one named region inside it.
  • Diagrams — all four carry role="img" and a title element. The exploded stack ships a narrow and a wide variant with a title id each, so the pages of each language hold five of those ids, and no page repeats one. Only the exploded stack also marks its decorative geometry aria-hidden; the other three leave it exposed to a reader walking the tree.

MOTION

Reduced motion is honoured globally, and nothing starts moving on its own.

Nothing on this site autoplays, loops, flashes or scrolls under its own power. The three films start only when a person presses play.

A prefers-reduced-motion: reduce block in the global stylesheet cuts every animation and transition on the page to 0.01ms and sets scroll-behavior to auto. It is unconditional, so anything added to the site later is covered without having to opt in.

One component animates: a twelve-pixel rise and fade as a block scrolls into view. It reads the media query in JavaScript before arming, and when reduced motion is set it never arms at all — the block is painted in its final position rather than transitioned there very quickly. It also declines to arm for anything already near the viewport on load, so nothing above the fold fades in.

  • Nothing flashes — no element blinks, pulses or loops, so the three-flashes threshold is never approached.
  • No motion is load-bearing — every reveal animates a block that is already in the document. With motion off, the page is complete.

THE ONE FORM

The request-access form is labelled, announced, and works with the script off.

Five fields, two of them optional. Each is a real label bound to a real control, and every field that has helper text carries it to a screen reader rather than only to the eye.

Every field has a label bound by for and id, its own maxlength and autocomplete, and helper text referenced through aria-describedby wherever there is helper text to reference. On /access and on /overview that is all five fields. On / the name field's helper is deliberately empty, and the field is then left with no aria-describedby at all rather than pointed at a node with nothing in it, which is the lesser of the two: a field with no helper text is no failure, and a reference that resolves to nothing would be one. A refused field gains aria-invalid and has its error appended to aria-describedby, so the error is read out with the field rather than announced somewhere else and lost. Validation runs on submit rather than on every keystroke, so a reader is not interrupted mid-field.

The outcome of a submission goes into a live region with role=status and aria-live=polite, which is where both a failure and a success are announced. Without JavaScript the submit button is marked aria-disabled and relabelled rather than carrying the disabled attribute, so it keeps its place in the tab order and a reader who reaches it is told it is unavailable instead of finding nothing there, and it points through aria-describedby at the alternative directly beneath it: the same request as a pre-filled email with every field's label already in the body. The form never silently does nothing.

  • Errors are text — a refused field is marked with a rule and a sentence, never with colour alone.
  • Optional is said — both optional fields say so in their own labels rather than relying on the absence of an asterisk on the others.

TEXT AND ZOOM

The type scale is in rem, and the smallest label is eleven pixels.

Nothing on the site is sized in a unit a browser's own text setting cannot move.

Body text is 1rem at a 1.65 line height, headings are clamp() expressions in rem that grow with the viewport, and text-size-adjust is pinned to 100% so a mobile browser does not inflate the body independently of everything around it. Raising the browser's base font size scales the whole page, headings included.

The exception is the mono label the site uses for eyebrows and status chips: 0.6875rem, which is eleven pixels at a default base, uppercase, with wide letter spacing. It clears the contrast bar everywhere it appears, and it is still smaller than this site should be asking anyone to read. It is named here because it is the one type decision on the site a person with low vision is most likely to hit first.

  • Measures are capped — prose runs to 62 characters and ledes to 58, so a line does not stretch past where the eye can find its way back.
  • Reflow is untested — the layout is fluid and sets no fixed widths, but it has not been measured at 320 CSS pixels or at 400 percent zoom.

WHAT IS NOT DONE

One Level A failure, one page with no site chrome, and no audit.

The honest list, worst first.

No third party has audited this site, and nobody who depends on a screen reader has been asked to try it. Everything above is our own measurement of our own code, which is worth less than one session with an assistive technology in somebody else's hands.

There is almost no automated accessibility check in the build either. Linting carries six jsx-a11y rules — alt text, ARIA prop names, ARIA prop types, unsupported elements, required role props, and role-supported props — and beside them stand two structural tests. One walks every page the build prerenders, in all three languages, and the 404 the server renders, and fails if any section in it is left without an accessible name. The other holds each translated page to its English counterpart: the same number of section landmarks, each named by the same kind of element, and the same set of ids, so a translated page that lost a section or renamed an id fails rather than passing a total. That pair is the whole of the automated coverage for anything measured on this page, and it guards two sentences of it. None of the eight would catch a missing label, a broken focus order, a contrast regression, or a film without captions. The failure at the top of this page shipped past all of them.

  • Failing — captions on the three films, WCAG 2.2 criterion 1.2.2 at Level A.
  • Open, the chrome-less page — /qr offers two landmarks, its main and one named region, where every other page offers four before its regions are counted, and no skip link — a design choice paid for in orientation rather than an oversight, and listed here because the price is real.
  • Open, reflow — 320 CSS pixels and zoom to 400 percent, neither of them measured.
  • Open, type size — the eleven-pixel mono label.
  • Not published — there is no VPAT and no conformance claim, because on the captions criterion there is nothing to claim yet.

FEEDBACK

Tell us what you hit, and we will tell you when it is fixed.

A barrier on this site is a defect on the same list as a wrong figure, and it is treated the same way.

Write to the address below with the page, the assistive technology you were using, and what happened. If what you need is what a film says, ask and we will send the narration as text rather than make you wait for the captions.

Where the inaccessible thing is a district's own record rather than our page — a scan with no text layer, a table that exists only as an image — tell us anyway. We will say which of the two it is and point you at the district that published it, because republishing it is theirs to do rather than ours.

The page, the assistive technology, and what happened. A reply within five business days, naming either what changed or when it will.