---
title: "Anatomy of a component page"
description: "The fourteen sections a shipped component page may carry, the eight it must, and the word budget it fits inside."
url: "https://opsinjs.pensievelabs.org/components/anatomy-of-a-component-page"
source: "https://opsinjs.pensievelabs.org/components/anatomy-of-a-component-page.md"
section: "Components"
kind: "handbook"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["page contract", "page template", "lean component page", "documentation structure"]
---

> 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.
> `<StubNotice>` IS THE EXCEPTION, AND IT IS THE ONE TO READ. It is a
> paired element rather than a self-closing one, and the text between
> its opening and closing tags is prose an author wrote, reproduced
> below word for word. That prose is where this page says whether the
> component has been reviewed. Read the children, not only the
> attributes.

<PageTemplate kind="handbook" />

## The short version [#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](../project/decisions/0024-the-component-page-is-lean.mdx) records what was cut.

Start from `content/_templates/component.mdx` or [Button](./button.mdx). 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 [#how-it-works]

### What opens the page [#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](../project/decisions/0025-the-audit-is-author-run.mdx) 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 [#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.

| #  | Section            | Written or generated | Required               | What it is for                                                                                                                                                                                                   |
| -- | ------------------ | -------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1  | Preview            | generated            | always                 | `<ComponentPreview>` and nothing else                                                                                                                                                                            |
| 2  | Installation       | generated            | always                 | `<ComponentInstall>` alone. Its manual tab hosts `<ComponentSource>`, the only place source appears                                                                                                              |
| 3  | Usage              | written              | always                 | The import line and the minimal JSX. Two code blocks, zero prose                                                                                                                                                 |
| 4  | When to use it     | written              | always                 | `<WhenToUse>` alone: at most four cases for, five against, one line each. Every *against* names the component to use instead                                                                                     |
| 5  | Clinical meaning   | written              | health categories only | What 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 |
| 6  | Anatomy            | written              | optional               | `<Anatomy>` where the component has two or more parts, one line each. `<CompositionTree>` only where the tree nests below the root's children                                                                    |
| 7  | Examples           | written + generated  | always                 | One H3 per example: a sentence or two on what it shows and which prop drives it, then the preview                                                                                                                |
| 8  | States             | written              | optional               | The five data states as a table, on a component that displays a reading. Left out where most rows would say not applicable                                                                                       |
| 9  | Content guidelines | written              | optional               | One `<DoDont>` pair, on a component that renders copy the product writes                                                                                                                                         |
| 10 | Accessibility      | written + generated  | always                 | What 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                                               |
| 11 | Data attributes    | written              | optional               | The styling contract, where the component stamps more than `data-slot`                                                                                                                                           |
| 12 | CSS variables      | generated            | optional               | Where the component declares a custom property of its own                                                                                                                                                        |
| 13 | API reference      | generated            | always                 | `<PropsTable>` from a named exported interface, plus a few sentences at most for a default or refusal the table cannot carry                                                                                     |
| 14 | Related            | written              | always                 | Two 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.

<Todo>
  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.
</Todo>

### Why the order [#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](./alert-banner.mdx) when the situation
calls for [Callout](./callout.mdx), so every entry on the *against* side names an
alternative.

### What is generated [#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 [#do-this]

* **Fill in the template**, or copy [Button](./button.mdx) 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](../health/two-colour-axes.mdx) beats a paraphrase that drifts.
* **Write a Related bullet as a sentence**, subject first, verb from the closed table in
  [Documentation templates](../handbook/contributing/documentation-templates.mdx):
  `- [Link](./link.mdx) is for navigation.`

## Not this [#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](../health/evidence-and-references.mdx).
* **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](./card.mdx) 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 [#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.

## Related [#related]

* [Components overview](./index.mdx) has the full roster.
* [Button](./button.mdx) is the exemplar every page follows.
* [RangeBar](./range-bar.mdx) has the fullest clinical contract, and the best section 5.
* [ADR 0024](../project/decisions/0024-the-component-page-is-lean.mdx) explains what was
  cut and why.
* [Documentation templates](../handbook/contributing/documentation-templates.mdx) covers
  every page skeleton.
* [Component checklist](../handbook/contributing/component-checklist.mdx) is the definition
  of done.
* [Proposing a component](../handbook/contributing/proposing-a-component.mdx) shows how a
  new id reaches the catalogue.
* [Release phases](../project/release-phases.mdx) says what `planned`, `shipped` and
  `deprecated` each promise.
