opsinjs
HandbookContributing

Proposing a component

The intake questions a proposal must answer before an API is worth discussing. The four reasons a proposal is normally rejected.

The short version

A proposal is a document, not a pull request, and it answers eight questions. Answer them in order and the API falls out of the answers; start with the API and the review will send you back to question one.

  1. What does it show, in one sentence a patient would understand?
  2. What does it assert about somebody's health? If the answer is "nothing", say so. That is a legitimate answer for a Surface and a wrong one for a ScoreDial.
  3. What must it never be read as? The prohibition, stated as plainly as the intent.
  4. When should somebody not use it, and what should they use instead? The alternative must be named. An entry with no alternative is not accepted.
  5. Which colour axis may it use, and who owns the thresholds? See The two colour axes.
  6. What is the accessibility bar? Keyboard contract, announcement contract, what survives grayscale, what happens at 200% text.
  7. What is the failure mode when the data is empty, stale, partial or wrong? Every one of the five data states, with an answer each.
  8. What already exists that this is not? Name the two closest components and the distinguishing line.

How it works

Triage comes before the eight questions are worth reviewing, and it asks three of its own.

Is it distinct? Name the closest existing component and say what it cannot do. "Like Callout but for warnings" is not distinct, because the status axis already carries urgency.

Is it health-shaped? opsinjs is not a general-purpose component library. There are excellent ones already, and the reason to add something here is that it carries a clinical meaning, a safety obligation or a plain-language contract that a general component would get wrong.

Would it be needed twice? One screen in one product is a good local component. Two unrelated products needing the same thing is a system component.

A proposal that clears those becomes a specification page at status: planned: the eight answers above, expanded into the component page's outline, with a proposed API as a fenced TypeScript block explicitly marked as a specification. At that point it has a URL and a badge, and it can be argued with in public. That is the expensive rung and the one that decides most proposals.

Moving from planned to shipped means code, and the bar for that is the component checklist.

The four common rejections

  • It is a variant of something that exists. A "warning card" is AlertBanner with a status. Adding it as a component gives the same idea two names and two behaviours.
  • It carries clinical meaning we cannot support. A component that returns an assessment, a triage disposition or a likely cause is outside the scope of this system. See Is opsinjs right for this project?.
  • It mixes the axes by construction. A component whose single variant prop selects both a metric family and a severity cannot be fixed by renaming the prop.
  • Nobody owns the thresholds. A component that shows a value against a boundary needs a named owner for that boundary. "The design system decides" is not an answer.

Do this

  • Bring a real screen. A screenshot or a sketch of the product surface that needs this, with real content in it, answers half the questions before they are asked.
  • Write the "when not to use" list first. It is the fastest way to find out whether the component is a real distinction or a restatement.
  • Propose the smallest thing. A component that does one job composes; a component with a mode switch usually should have been two.
  • Accept "no" as a good result. The commonest outcome is being told which existing component or pattern already does the job, which takes ten minutes rather than three months.
  • Say what it costs. Whether it forces a client boundary, what it depends on, and roughly how much it weighs. See Performance and bundle size.

Not this

  • Do not open with a props table. The API is the last question, and it is the easiest one once the first seven are answered.
  • Do not propose a component to hold a design opinion. If the point is that a certain layout is better, that is a pattern or a foundation page.
  • Do not name it after its appearance. RoundedGaugeThing will be wrong within a release. See Naming conventions.
  • Do not skip question 7. The empty, stale and partial states are where health components fail, and a proposal that has not thought about them is a proposal that has thought about the happy path.
  • Do not invent evidence for question 2. If the reason is a design opinion, mark it as one.
  • Do not propose a general-purpose primitive that a dozen mature libraries already ship better, and do not propose a rename of something that exists. Confusion about which component to use is a documentation defect, and the fix is the when-not-to-use list.
  • Do not justify a component with a screenshot of another product. Say what the person reading the screen has to decide, and the proposal usually improves.

Gotchas

  • A component id is a URL, a catalogue row, a registry name and a set of frontmatter references. Choosing it is a commitment; assert-ia will make the rename survivable but it is still a rename.
  • implements and governedBy must agree in both directions. A doctrine page listing your component and a component page not listing the doctrine page fails the IA check.
  • Aliases live in registry/catalogue.ts, not in your page. Global uniqueness is only maintainable with one owner.
  • A specification is reviewed by whoever the freshness rules name for its kind: design and engineering always, clinical for anything in a health category.

On this page