opsinjs
FoundationsAcross the system

Principles

Five rules that decide every other argument in opsinjs, each stated as a prohibition you can check rather than a value you can agree with.

Overview

Most design systems publish principles that nobody can fail. "Be clear." "Be human." A principle that cannot be violated cannot be used to settle an argument, which means it is decoration.

These five are written as prohibitions, because a prohibition is checkable. Each one names a specific thing you may not do, the harm it does to somebody reading their own health data, and the alternative you reach for instead. Each is also enforced somewhere, whether by a check that fails the build, by a generated report, or by a section that <PageTemplate> refuses to let you omit. Each enforcer named below is one that runs today; where a principle is enforced only by review, or by tooling that is specified and not built, this page says which.

The thing these are most often confused with is a style guide. A style guide tells you what opsinjs looks like. These tell you what opsinjs will not do even when it would look better.

How it works

1. One element, one axis. Colour in opsinjs carries two independent meanings, and a single element may draw from exactly one of them. Those meanings are which part of your health this is about (category) and how much attention it needs (clinical status). A tile that is both a heart tile and an urgent tile takes its status from the surface and its category from a glyph or a label, never from two competing reds.

Do

An urgent reading renders on the status surface, with the category carried by its icon and its heading. One colour, one meaning, and the reader can learn the rule in one screen.

Don’t

A heart-red border around an urgent-red fill. The reader has no way to know which red is telling them something, so both stop meaning anything. The next genuinely urgent screen then has nothing left to escalate with.

Enforced by: scripts/check-a11y.mts rule A11Y008, which fails pnpm check on any element under registry/ that carries both data-status and data-category, resolves both axes on one CSS property, or takes colour from both axes at all; and <StatusAxisDemo>, which refuses to render a mixed pair. The ESLint and Stylelint rules that would catch the same thing inside a consumer's own project are specified and not built. See Handbook → Tooling. Doctrine lives at The two colour axes.

2. Never colour alone. Every status distinction is carried by the word, a distinct glyph shape and colour, together. The word is the primary carrier and is never omitted; colour is the one that fails first. This is not a concession to colour-blind readers on the margin; it is what makes the system survive a greyscale printout, a phone in sunlight, a screenshot pasted into a message, and a screen reader.

Do

A pill that reads Urgent with a filled warning glyph on the urgent surface. Take the colour away and the sentence still parses.

Don’t

A bare coloured dot beside a number. It is the most common status affordance in consumer health apps and it conveys nothing to a screen reader, nothing in print, and nothing to roughly one man in twelve.

Enforced by: scripts/check-a11y.mts rule A11Y001, which fails a file under registry/ that paints a status without rendering a word and a distinct glyph beside it, and warns where those come from a component it composes rather than from the file itself; and the generated CVD audit rendered by <CvdSimulator> and reported on Colour independence.

3. Say what you do not know. Missing, stale, estimated and partial data are first-class states with their own rendering, not a blank space or a zero. A health surface that shows nothing where a number should be is indistinguishable from a health surface showing a steady reading, and the reader will assume the latter.

Enforced by: Data states, which every data-bearing component must implement in full, and the States table on every component page that displays a reading.

4. Tokens, not values. No component, example, page or generated file contains a literal colour, radius, duration or spacing value. Everything resolves through a token, and every token resolves through a scale. This is what makes a theme a theme rather than a find-and-replace, and it is why the contrast floor can be measured at all: you cannot audit a hex code that only exists inside one component.

Enforced by: scripts/check-a11y.mts rules A11Y005 and A11Y006, which fail pnpm check on a hex colour, a raw oklch(), a Tailwind palette class or a numbered axis ramp step anywhere under registry/, with A11Y007 warning on a neutral ramp step; and the fact that every value printed anywhere on this site comes from scripts/build-tokens.mts. The Stylelint rule that would extend the same check to a consumer's own stylesheets is specified and not built.

5. The system does not practise medicine. opsinjs decides how a number is presented. It never decides what the number means. Thresholds, reference ranges, triage, escalation policy and any wording that carries clinical meaning belong to the product and its clinical governance, and every surface that could be mistaken for a judgement must say whose judgement it is.

Enforced by: the Clinical meaning section, mandatory on every health-* component page; the governedBy frontmatter; and Safety, scope and limitations.

Using it

Use these to close arguments, in order. When two proposals conflict, the one that violates a lower-numbered principle loses. That ordering is deliberate and it is opinion, not evidence: axis purity comes first because it is the invariant that everything else in the colour system depends on, and a system that has quietly lost it cannot recover without a breaking change.

Two practical habits follow from them:

  • Write the prohibition into the ticket. "Add a red border for high readings" is a request that violates principle 1. Rewriting it as "escalate this reading to the attention level" makes it a request the system can actually satisfy.
  • Ask what the reader would do wrongly. Every one of these five is derived from a specific wrong action a worried person might take. If you cannot name the wrong action, you are probably arguing about taste, and taste is settled in Theming, not here.

Occasionally a principle genuinely has to bend. When one does, the obligation is to record it as an architecture decision, not to bend it quietly.

Tokens

Principles own no tokens; they constrain every family that does. Principle 4 is the one with a direct token consequence, and it is checkable from the generated side: if a value appears in a component and not in Reference → Tokens, the component is in breach.

Accessibility impact

Principles 2 and 3 are accessibility requirements wearing a design-principle costume, and stating them here rather than only in the Accessibility pillar is deliberate: a rule that lives exclusively in an accessibility document gets read by the person doing the audit and not by the person building the component.

  • Principle 2 is the practical form of WCAG 2.2 SC 1.4.1 Use of Colour, and it is checked mechanically rather than by inspection.
  • Principle 3 protects readers using screen readers and readers with cognitive disabilities equally: an absent value announced as nothing is a silent failure in both directions.
  • Principle 4 is what makes conformance measurable at all. Measured figures are produced by pnpm contrast and rendered by <ContrastReport> on Contrast conformance.
  • The two colour axes covers principle 1 in full, with the clinical reasoning behind it.
  • Data states covers principle 3 turned into a concrete set of states every component must render.
  • Token architecture covers principle 4 turned into a three-tier structure with rules about which tier may reference which.

On this page