opsinjs

Accessibility

What opsinjs guarantees, what your product still owns, and how to check either one without taking our word for it.

What we guarantee

A design system cannot make a product accessible. It can make the accessible version the cheap one, and it can refuse to hand you the parts that make an inaccessible screen easy to build. That is the guarantee here, and it is deliberately narrower than "opsinjs is accessible".

Four commitments hold across everything in this system, and each one is checkable rather than asserted:

  1. Clinical status is never carried by colour alone. Every status surface emits a word and a data-status attribute alongside its colour. Reading the page in greyscale, or through a screen reader, or on a photocopy, must give the same answer as reading it in colour. This is WCAG 2.2 SC 1.4.1 Use of Colour, but the reason we treat it as a safety rule rather than a conformance rule is on Colour independence.
  2. The touch-target floor is 44 CSS pixels, not 24. WCAG 2.2 SC 2.5.8 sets 24×24 as the AA minimum. We ship the AAA figure from SC 2.5.5 as the floor, for reasons that are specific to health apps and are set out on Target size and motor accessibility.
  3. Every animated token carries a reduced-motion value. Reduced motion is a per-token degradation, not a global kill switch, so a component still changes state. It simply arrives immediately. See Reduced motion.
  4. Every accessibility number published on this site is generated, dated and reproducible. Contrast figures come out of scripts/check-contrast.mts and are committed; they are not typed by an author. Where the generator has not run, the page says so instead of showing a plausible figure.

Every catalogue row is implemented and installable. That changes what these commitments cover, though less than it sounds. They cover the token layer, the CSS in app/globals.css and app/product.css, and the page contracts every component specification must satisfy. For all sixty, they also cover whatever pnpm run check:a11y can prove by reading a component source. Every one of the sixty has now been audited against WCAG 2.2 AA (author-run), in a source pass and a rendered pass, and the findings were fixed in that same change. That audit was run by the authors of the components, so it is not an independent review, and no clinician has reviewed any of the sixty. Read ADR 0025 for what the audit was and what it was not.

What you own

The boundary matters more than the promise. These are yours, and none of them can be moved into a design system without the system pretending to know things about your product that it cannot know.

  • Accessible names. A Button gives you focus handling and a target size. It cannot know that "Continue" needs to be "Continue to your results" for someone arriving at it out of context.
  • Reading and focus order. The DOM order is written by you. A visually correct two-column layout with a scrambled tab order passes every automated check we could ship.
  • Thresholds and clinical meaning. opsinjs will render "attention" in the attention colours, with its own glyph shape and the word "Needs attention". Deciding that a systolic reading of a particular value is "attention" is a clinical decision owned by your product and whoever signs off its clinical safety case. See Safety, scope and limitations.
  • Announcement politeness. We can specify that a status change must be announced. Only you know whether the change happened because the reader submitted something (announce it) or because a background sync completed (usually do not interrupt them).
  • Timing, sessions and authentication. SC 2.2.1, 3.3.8 and 3.3.9 are product decisions. A design system that shipped a session timeout would be shipping a clinical risk.
  • Your content. Everything under Content and language is guidance we can hold you to at review; it is not something the components can enforce.

How to check

Start from the role you are actually in. The five role pages are the same standard seen from five desks, and each one is a list of things you can do today rather than a restatement of WCAG.

  • For designers covers contrast, target size, focus appearance and colour independence, checked before handoff.
  • For developers covers semantics, names, roles, state and focus order, checked in the browser.
  • For content covers headings, link text, alt text and reading level, checked while writing.
  • For testing has the manual and automated passes, and how to reproduce each one.
  • For compliance reviewers is the curated twenty-minute path, written to be printed.

Then use the per-topic pages for the specific question. Each of those carries its own reproducible check with an expected result and a description of what failure looks like, because "test with a screen reader" is not a test.

Measured results

60 shipped

Contrast is the accessibility property this system measures most confidently, because it is a property of the tokens rather than of a component: pnpm run contrast computes it without rendering anything. The measured APCA Lc and WCAG 2.2 ratios for every published token pair appear on Contrast conformance, dated, in both themes.

It is no longer the only one. pnpm run check:a11y reads every component source on each pull request and fails on a status carried by colour alone, a px type size, a raw colour literal, both colour axes on one element, a banned word in copy or in an identifier, and a role that exists only under the documentation chrome. It reads text, not a rendered page, so it can prove a component does not do those things and cannot prove that what it renders is usable. The layout-dependent half is hit area, 1.3× and 200% against the built /view routes. That half runs nightly, on a schedule, which means it goes red the morning after a merge rather than before it.

Every other measured result on this site is currently absent, and the pages that would carry those numbers say so in words rather than filling the gap.

Known gaps

These are real, and they are listed here rather than in an internal tracker because a conformance claim with no gaps section is not a conformance claim.

  • No component has been tested with a screen reader. All sixty were audited against WCAG 2.2 AA by their authors, in a source pass and a rendered pass, but that rendered pass never put one of them in front of a screen reader. Screen readers states the intended tested matrix and marks every cell untested.
  • prefers-contrast is not answered in the token layer. app/globals.css handles prefers-reduced-motion and prefers-reduced-transparency today and carries no increased-contrast block. One component has gone ahead of it: Surface drops its translucency and widens its edge under prefers-contrast: more in its own utilities, unmeasured, and the colour half of the escalation is answered nowhere. See Increased contrast for what the token layer will change and Surface for what already changes.
  • The automated checks read source and layout, never comprehension. Two now run. scripts/check-a11y.mts blocks every pull request and reads the component sources as text. It can prove a file imports a glyph and reads the status vocabulary, and it cannot prove either one reaches the screen. Its --layout half runs once a night against the built /view routes and measures three things: hit area, survival at 1.3x, and 200% with no truncated value. Neither half is an axe pass, neither has met a screen reader, and neither looks at this documentation site.
  • No independent conformance evaluation has been performed. The author-run audit against WCAG 2.2 AA is not an ACR, and the conformance report is still a complete ACR skeleton in which every criterion is marked Not Evaluated, which is the honest state.
  • Cognitive accessibility guidance is not yet backed by user research. Cognitive accessibility is marked as opinion throughout.

Updates to this page

Last read through against the system on 2026-09-20. Due for review every 6 months; expiry is reported by pnpm run check:freshness.

On this page