---
title: "Principles"
description: "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."
url: "https://opsinjs.pensievelabs.org/foundations/principles"
source: "https://opsinjs.pensievelabs.org/foundations/principles.md"
section: "Foundations"
kind: "foundation"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["design principles"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="foundation" />

## Overview [#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 [#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.

<DoDont>
  <DoDont.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.
  </DoDont.Do>

  <DoDont.Dont>
    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.
  </DoDont.Dont>
</DoDont>

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](../handbook/tooling/index.mdx). Doctrine lives at
[The two colour axes](../health/two-colour-axes.mdx).

**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.

<DoDont>
  <DoDont.Do>
    A pill that reads `Urgent` with a filled warning glyph on the urgent surface.
    Take the colour away and the sentence still parses.
  </DoDont.Do>

  <DoDont.Dont>
    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.
  </DoDont.Dont>
</DoDont>

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](../accessibility/colour-independence.mdx).

**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](./data-states.mdx), 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](../start/safety-scope-and-limitations.mdx).

## Using it [#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](../theming/index.mdx), not here.

Occasionally a principle genuinely has to bend. When one does, the obligation
is to record it as an
[architecture decision](../project/decisions/index.mdx), not to bend it quietly.

## Tokens [#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](../reference/generated/tokens.mdx), the component is in
breach.

## Accessibility impact [#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](../accessibility/contrast-conformance.mdx).

## Related [#related]

* [The two colour axes](../health/two-colour-axes.mdx) covers principle 1 in
  full, with the clinical reasoning behind it.
* [Data states](./data-states.mdx) covers principle 3 turned into a concrete set
  of states every component must render.
* [Token architecture](./token-architecture.mdx) covers principle 4 turned into
  a three-tier structure with rules about which tier may reference which.
