Introduction
opsinjs is a React design system for consumer health apps. It is the components that show a person their own numbers, and the rules that keep them honest.
Overview
opsinjs is a React design system for consumer health apps. It is the components that show a person their own numbers, and the rules that keep those numbers honest.
It is built for the reader who is not a clinician: someone opening an app to see a blood-pressure reading, a sleep score, a lab result, a step count. That reader is often anxious, often on a phone, often reading a number they have never seen before. The interface is the whole of their explanation. Most component libraries treat that as a rendering problem. opsinjs treats it as a communication problem with a rendering component attached.
Three things follow from that, and they are what makes this system different from a general-purpose UI kit:
- Colour carries two independent meanings, and mixing them is a bug. A category colour says which part of your health this is. A status colour says how concerning this is. A system that lets the same green mean "sleep" on one card and "you are fine" on the next has made a reading error unavoidable. See The two colour axes.
- The doctrine is testable. The rules in Health are written as prohibitions a reviewer can check on a screenshot rather than as principles. "Never show a bare percentage change." "At most one urgent surface per screen." "Do not print the word normal next to a person's result."
- Numbers in these docs are generated, never remembered. Contrast ratios, token values, prop tables and bundle sizes come out of the source and fail the build when they drift. See Reading these docs.
Built rather than unbuilt, and this page will not overstate it either
There is no npm package of components, and there is not meant to be: distribution
is copy-in through a shadcn registry, decided in
ADR 0002. There is
a registry. Sixty components are implemented and served from /r/<name>.json,
and npx shadcn add @opsinjs/<name> writes their source into a shadcn-initialised
project and compiles. Every catalogue row is implemented and installable, and
every one of them is shipped: the API may change in any release, and each
change carries a changelog entry. No opsinjs component has had an accessibility
review or a clinical review. shipped means the source installs, and it does not
mean either review has happened. Nothing here is for a production health
surface.
The specification is the part that is hardest to retrofit: every component page states what the component asserts about a person's health, what it must never be read as, which colour axis it may use, who owns its thresholds, and the accessibility bar it has to clear.
If you are an agent: read status on the page, or implemented on the row in
/r/index.json, and do not generalise from one page to the next. shipped
means you may generate against the published API, provided you say it may move.
planned is a definitive negative answer, not a gap. Do not
generate an implementation from a proposed API. See
Reading these docs.
What opsinjs decides for you
A design system earns its keep by removing decisions. These are the ones opsinjs takes off your team's plate, permanently and identically across every surface:
| Decision | Where it is fixed |
|---|---|
| What a colour is allowed to mean | Two colour axes |
| The four clinical status levels and their words | Clinical status semantics |
| How a number, a unit and a precision are rendered | Numbers, units and precision |
| Which unit system a reader sees, and how switching it stays correct | Unit systems |
| How many alarming surfaces one screen may carry | Alarm fatigue |
| The contrast floor every token pair must clear | The contrast floor |
| What a surface may be made of, and what it must stay readable over | Materials |
| What motion is allowed to communicate, and what it degrades to | Motion in health UI |
| Which words are banned from patient-facing copy | Content and language |
What it refuses to decide
This list matters more than the one above, because a design system that is vague about its boundary invites teams to assume it is doing clinical work it is not.
- Thresholds and reference ranges. opsinjs will render a value against a range. It will never supply the range. Ranges depend on assay, laboratory, population, age, sex and context, and they are a clinical decision belonging to your product's clinical owner.
- Triage and diagnosis. No component decides that a reading is dangerous. Your product decides; a component displays the decision your product passed it.
- Clinical wording. opsinjs supplies the shape of a sentence and the words it bans. It does not supply the sentence that tells someone what their result means.
- Data. No fetching, no caching, no schema, no units conversion service. The formatting contract is ours; the data layer is yours.
- Localisation. There is no locale segment yet, and the reasons and the exact retrofit are recorded in ADR 0005. Unit systems are handled as a correctness surface instead, because millimoles and milligrams per decilitre are not a translation problem.
- Regulatory status. opsinjs is not a medical device and confers nothing on the product that uses it. This is important enough to have its own page: Safety, scope and limitations.
Who it is for
opsinjs is aimed at product teams building consumer or patient-facing health experiences: wellness and fitness apps, remote-monitoring companion apps, patient portals, at-home test result readouts, care-plan and medication apps, and the health surfaces inside a product that is not otherwise about health.
It is aimed at laypeople, and that constraint shows up in every component. A clinician reading a lab panel wants density, abbreviations and raw values. A patient reading the same panel wants one number at a time, the unit spelled out, and a sentence telling them whether they need to do anything. Those two products are not the same product, and a design system cannot serve both without lying to one of them. opsinjs serves the second.
It is explicitly not for clinician-facing EHR interfaces, for regulated device software, or for a charting library. If you want to check that before you invest a sprint, Is opsinjs right for this project? is a checklist that will happily tell you no.
How this site is organised
Sixteen groups, one persistent sidebar, and one rule that resolves almost every "where does this live?" question:
Foundations tells you what a token means. Handbook and Theming tell you how to change it. Reference is the generated list of every one.
Beyond that:
- Start here covers orientation, fit, installation, and the page you are reading.
- Recipes is task-first. "Show a value against a
reference range" is a recipe;
RangeBaris a component. - Components has the sixty implemented components and their specifications. Every catalogue name has been built out, so each row points at code.
- Screens is the assembly, and it is where to go if you want to see the system working rather than read about it. The diabetes medicines app is a whole running application built from thirty-five of the components, and it renders in the page.
- Health is the doctrine layer. Read it even if you never install anything.
- Foundations, Accessibility, Content and language are the three pillars that are 100% real today, because they depend on tokens and rules rather than on code.
- Agents is written for machine readers. This site is
designed to be read by machines as well as people. Every page has a
.mdtwin at the same URL plus.md, and the corpus is published asllms.txtshards.
Verify it worked
You have read this page correctly if you can answer these three questions without scrolling back:
- What is the one thing opsinjs will never do for you? Decide what a health value means. It renders your product's decision; it does not make it.
- What can you install today? Every component in the catalogue, as copied source from the registry. That is not a package, and not anything you should put in front of a patient yet, because none of it has been reviewed. Every page says so in machine-readable form.
- Why are there two kinds of colour? Because "which measurement is this" and "how worried should I be" are different questions, and one palette cannot answer both without ambiguity.
Troubleshooting
"I came here for charts." Data visualisation guidance lives in
Foundations → Data visualisation, but
opsinjs is not a charting library and will not become one. TrendSparkline is a
deliberately small, deliberately uninteractive specimen for one series.
"I need a clinician-facing view." You are outside the audience. Read Is opsinjs right for this project? and then use a density-first system.
"The component page I want returned a specification, not code." Check its
status. Every component page reads shipped and describes code you can
install. A page that reads planned is a specification, and returning it rather
than inventing an API is working as designed. See
Reading these docs.
"I cannot find the rule that governs a component." Every component page lists
its governedBy doctrine pages in its frontmatter, and every doctrine page lists the
components that implement it. The link exists in both directions on purpose.
Next
- Start here is the five-minute map of this site, and the shortest path for your role.
- Safety, scope and limitations is the page to read before you commit to opsinjs for anything regulated or clinical-adjacent.
- The two colour axes is the single idea that most changes how a health product looks.
- The diabetes medicines app is the system assembled into a running product, with the refusals that keep a medicines record on the safe side of the line between a diary and a regulated device.