opsinjs

Anatomy of a component page

The fourteen sections a shipped component page may carry, the eight it must, and the word budget it fits inside.

The short version

A shipped page is fourteen sections in a fixed order: eight required, five optional, one gated on category. <PageTemplate kind="component" /> and assert-ia both fail the build on a missing required section, an unknown one, or one out of order.

Rule LEAN001 fails the build when the body passes 1,000 words, or 1,250 on a health-* page. The lean page keeps every contract and drops every argument, and ADR 0024 records what was cut.

Start from content/_templates/component.mdx or Button. Never start from a blank file, and never from a health component's page for a component that is not one, because that copies a clinical contract onto something it does not govern.

How it works

What opens the page

There is no ## Status heading. <StubNotice status="shipped"> sits above ## Preview, first after the definition, and carries at most three open safety questions, one sentence each. SAFE001 requires its sentences in authored MDX, never a generated string: audited against WCAG 2.2 AA by the authors and not independently reviewed, clinical review still pending, and not for a production health surface until a clinician signs it. See ADR 0025 for the record. C6001 fails the build when the notice is missing or sits below a heading. The H1 and the definition come from title and description, never from MDX.

The fourteen sections

SHIPPED_SECTIONS in lib/status.ts fixes the numbering, which is the order a page must present. Five are optional: Anatomy, States, Content guidelines, Data attributes and CSS variables. Each earns its place by the test in its row and keeps this position when it appears. Omitting one is not a defect; inventing content to fill it is.

#SectionWritten or generatedRequiredWhat it is for
1Previewgeneratedalways<ComponentPreview> and nothing else
2Installationgeneratedalways<ComponentInstall> alone. Its manual tab hosts <ComponentSource>, the only place source appears
3UsagewrittenalwaysThe import line and the minimal JSX. Two code blocks, zero prose
4When to use itwrittenalways<WhenToUse> alone: at most four cases for, five against, one line each. Every against names the component to use instead
5Clinical meaningwrittenhealth categories onlyWhat it asserts, what it must never be read as, which colour axis reaches which element, who owns the thresholds, one or two sentences each. Required when category starts with health-, forbidden otherwise
6Anatomywrittenoptional<Anatomy> where the component has two or more parts, one line each. <CompositionTree> only where the tree nests below the root's children
7Exampleswritten + generatedalwaysOne H3 per example: a sentence or two on what it shows and which prop drives it, then the preview
8StateswrittenoptionalThe five data states as a table, on a component that displays a reading. Left out where most rows would say not applicable
9Content guidelineswrittenoptionalOne <DoDont> pair, on a component that renders copy the product writes
10Accessibilitywritten + generatedalwaysWhat a gate checks, what is argued from the source, what nothing checks, labelled apart. Then the keyboard table and the contrast report. Never delegated upstream
11Data attributeswrittenoptionalThe styling contract, where the component stamps more than data-slot
12CSS variablesgeneratedoptionalWhere the component declares a custom property of its own
13API referencegeneratedalways<PropsTable> from a named exported interface, plus a few sentences at most for a default or refusal the table cannot carry
14RelatedwrittenalwaysTwo to five one-line bullets naming what the component is confused with, each pointing at the alternative

A planned page is eight sections: Preview, Installation, When to use it, Clinical meaning (health categories only), Anatomy (optional), Proposed API, Accessibility and Related. ## Proposed API replaces ## API reference, written as a specification and marked do-not-generate-against, and the two never both appear.

Not written yet.

The page header is specified to carry more than it renders. The status badge, the category, the base and style switcher and Copy Page are all specified in this handbook's history and are rendered by nothing: PageActions exists in components/mdx.tsx and has zero invocations in the corpus, and PageTemplate emits a machine-readable contract script and its children and nothing visible. A footer carrying a changelog link, an edit link and <Feedback /> is likewise specified and absent from every component page. Either build the header and the footer, or delete the components that promise them.

Why the order

Installation is section 2 so a developer who came for the install command finds it at once, with only the notice above it. When to use it is section 4 because the expensive mistake is reaching for AlertBanner when the situation calls for Callout, so every entry on the against side names an alternative.

What is generated

Prop tables, CSS-variable tables, contrast reports and example source come from scripts/*.mts. A page carries names, not code and not measured numbers, and CI fails on a regeneration diff.

Do this

  • Fill in the template, or copy Button and replace every fact.
  • Write the definition for the patient, not the engineer. That one-sentence description ends up in search results, in llms.txt and under the H1.
  • Name an alternative in every prohibition. An avoid entry takes an instead, and the value is a checked catalogue id.
  • Declare governedBy on every health-* component, and have the doctrine page list it back. assert-ia checks both directions.
  • Take aliases from the catalogue. registry/catalogue.ts owns the alias namespace.
  • Say what the component refuses, in one line. What it renders when the range is unknown, what it will not derive, what it must never be read as.
  • Link doctrine rather than restating it. One sentence and a link to Two colour axes beats a paraphrase that drifts.
  • Write a Related bullet as a sentence, subject first, verb from the closed table in Documentation templates: - [Link](./link.mdx) is for navigation.

Not this

  • Do not write an essay under a heading. A section is a component call or a short paragraph. Doctrine goes on the page that owns it, reasoning in the commit message.
  • Do not leave a heading empty, and do not write TODO: fill this in. Omit the section, or use <Todo> when the gap is real and must be tracked.
  • Do not paste code into MDX. <ComponentSource> and <ComponentInstall> resolve a name against the registry and render the real thing.
  • Do not state a measured number you did not generate. No contrast ratios, no invented percentages. <NoDataYet> is the honest rendering of a table with no source.
  • Do not invent a citation. Not a study, not a statistic, not a DOI, not a date. See Evidence and references.
  • Do not add a section this contract does not have. A page that genuinely needs one changes lib/status.ts and the template together.
  • Do not give a non-health component a clinical meaning. Card is a box, and a verdict hidden inside a box escapes review.
  • Do not grow the notice. Three open questions, one sentence each. The rest stay in git history, still open.

Gotchas

Links are relative. Write [Two colour axes](../health/two-colour-axes.mdx). fumadocs resolves it through createRelativeLink. An absolute site link is banned with no exception.

The MDX vocabulary is closed. Only the tags in this contract exist. Content authors never define one; an unknown JSX tag fails the build.

Component ids are kebab-case in every path, frontmatter field and prop; PascalCase only in prose. range-bar in usedIn, RangeBar in a sentence.

British spelling in prose, American in code. colour and behaviour in what a reader reads; color, ColorScale and --color-* in code.

aliases are globally unique, not unique per page. If two pages claim gauge, the build fails on the second page rather than the wrong one.

The word budget counts everything in the body. JSX props, keyboard rows and code blocks count too. A page over budget has a section arguing rather than stating.

A planned page is a specification, not a placeholder. If you cannot say what the component asserts, what it refuses, how it composes and what it must never be read as, it is not ready for a page.

On this page