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 contrastand rendered by<ContrastReport>on Contrast conformance.
Related
- 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.
Foundations
The ten token families and six system-wide contracts that every opsinjs component is obliged to obey, and what each one decides on your behalf.
Token architecture
The three tiers a token can live in, the one direction references are allowed to point, and why a component may never reach past the semantic layer.