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.
| # | 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.
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
descriptionends up in search results, inllms.txtand under the H1. - Name an alternative in every prohibition. An
avoidentry takes aninstead, and the value is a checked catalogue id. - Declare
governedByon everyhealth-*component, and have the doctrine page list it back.assert-iachecks both directions. - Take aliases from the catalogue.
registry/catalogue.tsowns 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.tsand 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.
Related
- Components overview has the full roster.
- Button is the exemplar every page follows.
- RangeBar has the fullest clinical contract, and the best section 5.
- ADR 0024 explains what was cut and why.
- Documentation templates covers every page skeleton.
- Component checklist is the definition of done.
- Proposing a component shows how a new id reaches the catalogue.
- Release phases says what
planned,shippedanddeprecatedeach promise.
Components
Every component opsinjs has specified, with the status of each, listed in one table so nobody has to guess whether something exists.
ResultCard
One test result, shown in full. What was measured, the number, where it sits against its range, what that means in plain English, and what to do next.