---
title: "Documentation templates"
description: "Twelve page kinds, each with a fixed set of headings the build enforces. The outlines are published verbatim, so a contributor fills in a template."
url: "https://opsinjs.pensievelabs.org/handbook/contributing/documentation-templates"
source: "https://opsinjs.pensievelabs.org/handbook/contributing/documentation-templates.md"
section: "Handbook"
kind: "handbook"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["page templates", "section outlines", "page kinds", "what headings does this page need"]
---

> 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 page's `kind` fully determines its headings.** Authors do not invent
sections, do not reorder them, and do not leave one empty. `assert-ia` compares
the H2s in every page against the outline for its kind and fails the build on a
missing or an unexpected one.

That sounds heavy-handed and it is the reason four hundred pages stay navigable.
A reader who has read one health page knows where the boundary statement is on
every other one; an agent that has parsed one component page knows where the
prohibition list lives on all sixty.

The templates themselves live in `apps/www/content/_templates/`. There is one
`.mdx` per kind, kept outside `content/docs/` so they are never routed. Copy
one; do not start from a blank file.

## How it works [#how-it-works]

Twelve kinds. The outlines are reproduced here verbatim, in order. Where a
section is generated, it is marked.

### `component` [#component]

The lean anatomy, taught once on [Anatomy of a component
page](../../components/anatomy-of-a-component-page.mdx). `<StubNotice>` opens
the page above the first H2, and there is no Status heading. The sections that
render are **gated by status**: at `planned` the page shows Preview,
Installation, When to use it, Clinical meaning (`health-*` only), Anatomy
(optional), Proposed API, Accessibility and Related. `shipped` adds Usage,
Examples and API reference, and may also carry States, Content guidelines, Data
attributes and CSS variables where there is something to say. `deprecated` owes
the same outline as `shipped`. Sections not required at the declared status are
**omitted, not left empty**, and the body is held to a thousand words, or twelve
hundred and fifty on a `health-*` page.

### `foundation` [#foundation]

`## Overview` → `## How it works` → `## Using it` → `## Tokens&#x60; &#x2A;(generated)* →
`## Accessibility impact` → `## Related`

### `health` [#health]

`## What this means` → `## The rule` → `## Why (evidence)` → `## Applying it` →
`## Components that implement this&#x60; &#x2A;(generated from `implements`)* →
`## What this does not cover` → `## Updates to this page`

`evidence` and `reviewed` are mandatory frontmatter on this kind.

### `accessibility` [#accessibility]

`## What we guarantee` → `## What you own` → `## How to check` →
`## Measured results&#x60; &#x2A;(generated)* → `## Known gaps` →
`## Updates to this page`

### `content` [#content]

`## The rule` → `## Approved / Rejected` → `## Patterns` → `## Banned words` →
`## Related components`

### `pattern` [#pattern]

`## When to use` → `## When not to use` → `## How it works` → `## Content` →
`## Accessibility` → `## Research` → `## Updates to this page`

Every entry under "when not to use" names the alternative.

### `recipe` [#recipe]

`## The task` → `## What you need` → `## Build it` → `## The copy` →
`## Get it right` → `## Variations` → `## Related`

### `screen` [#screen]

`## What this screen does` → `## Composition` → `## Preview` →
`## Safety notes` → `## Accessibility` → `## Status`

### `handbook` [#handbook]

`## The short version` → `## How it works` → `## Do this` → `## Not this` →
`## Gotchas` → `## Related`

This page is one.

### `reference` [#reference]

Generated body only, plus a hand-written header block naming the source file and
the script that produces it.

### `project` [#project]

Free-form headings, but every page ends with `<LastUpdated />` and
`<Reviewed />`.

### `guide` [#guide]

`## Overview` → one H2 per task, named by the author → `## Verify it worked` →
`## Troubleshooting` → `## Next`

The four fixed sections and their order are the contract; the task sections in
between are yours.

## Do this [#do-this]

* **Copy the template file for your kind.** `content/_templates/<kind>.mdx`.
* **Fill every section.** On every kind but `component`, a section with one
  sentence under it is a signal that either the page or the kind is wrong. On a
  component page one sentence is often the whole answer.
* **Use `<Todo>` for a genuine gap.** It is counted in the build's coverage
  report, which is the point: incompleteness is measured rather than hidden.
* **Use the closed MDX vocabulary.** The tags listed in the component anatomy
  are the only ones that exist; `assert-ia` fails the build on an unknown JSX
  tag, and content authors never define one.
* **Write relative links with the `.mdx` extension.**
  `[Two colour axes](../../health/two-colour-axes.mdx)`. fumadocs'
  `createRelativeLink` resolves them.
* **Write a related-links bullet as a sentence.** The linked page or the bold
  term is the subject, and the verb comes from a closed table. A page that is
  the thing its gloss names takes `is`, a page that holds the thing takes
  `has`, and where neither reads true the verb is `covers`. A gloss opening
  with `what`, `which`, `whether` or `when` takes `says`. One opening with
  `where` takes `is where`, which absorbs the `where` out of the gloss. `why`
  takes `explains`, `how` takes `shows`, and any other bare noun phrase takes
  `covers`. A gloss that already opens with a finite verb or a modal takes no
  added verb at all. Where the gloss is not a predicate of the link, give the
  link a short predicate of its own and put the gloss into a second sentence.
  The link text stays the page title, the gloss keeps its wording and its
  lower-case first letter, the item ends in a full stop, and no colon stands
  between the link and its gloss.

## Not this [#not-this]

* **Do not add a heading the outline does not have**, however good it is. If a
  kind genuinely needs a new section, change the kind's outline in one place and
  every page that uses it.
* **Do not leave a required section empty** to satisfy the check. An empty
  section is a worse answer than `<Todo>`, because `<Todo>` is counted.
* **Do not use an absolute site link in MDX.** Write a relative file link and
  let fumadocs resolve it. There is no exception left: the one that used to
  exist, the Sections rail in the root `meta.json`, was deleted with the rail.
* **Do not restate a canonical page.**
  [Plain-English A to Z](../../content/plain-english-a-z.mdx) is canonical for
  plain language; [Numbers, units and
  precision](../../health/numbers-units-precision.mdx) is canonical for numeric
  formatting. Link, do not repeat. `assert-ia` checks for restatement.
* **Do not hand-write a generated section.** Tokens, contrast numbers, keyboard
  tables, prop tables and the catalogue are produced by `pnpm run generate`.

## Gotchas [#gotchas]

* **The H2 check is exact.** "Not this" and "What not to do" are not the same
  heading, and the build says so.
* **`kind` is not a category.** It selects an outline. A page about health that
  is really a step-by-step is `kind: guide` living under `start/`, not
  `kind: health` with the wrong headings.
* **Status gating applies only to `kind: component`.** Every other kind renders
  its full outline at every status.
* **The templates are outside `content/docs/`** on purpose. Moving one inside
  creates a route and a page with placeholder prose in the search index.
* **The frontmatter schema and `source.config.ts` must agree.**
  `content/_templates/frontmatter.schema.json` is the machine-readable form;
  `source.config.ts` is what the build actually enforces.

## Related [#related]

* [Contributing](./index.mdx) has the routes in and what each has to clear.
* [Anatomy of a component page](../../components/anatomy-of-a-component-page.mdx)
  is the component page contract in full.
* [Code style](./code-style.mdx) has the conventions that apply inside the
  templates.
* [Reading these docs](../../start/reading-these-docs.mdx) covers the same
  contracts, explained for a reader rather than a contributor.
