Lesson 14 / 19
Documentation
The required sections of component documentation, the information a counter-example carries, computing documentation coverage at the section and component level, and comparing the prop table against the interface.
Contents
Once names match, it becomes possible to talk about a component, but that is not
enough to use it. Which state calls for dropdown instead of search-field, when a
modal gives way to a separate page instead, and the distinction between badge and
notification-banner cannot be read from names.
Documentation carries these decisions. Component documentation is not a usage guide; it is the readable form of the component’s contract: what it does, what it does not do, what input it takes, when it should not be chosen. This lesson defines the doc’s required sections and shows how to measure coverage.
The Doc’s Required Sections
A component doc’s sections correspond to the questions the person using it asks in order:
- Purpose. What job does the component do? One sentence. A component whose purpose takes two sentences to explain is usually two components.
- When to use. In which context it is the right choice. This section does not describe the component; it gives a selection criterion.
- When not to use. The counter-example. It says which component to use instead.
- Prop table. The names of the props, the values they accept, their defaults, and whether they are required.
- Example. Usage that can be copied and run. Source text, not a picture.
- Accessibility note. Keyboard behavior, focus order, and the line between what the component provides on its own and what the person using it must provide.
- Related components. Links to neighboring components; the person using it may have started from the wrong place.
Six of these seven sections are familiar. Across most systems, the two most often missing are the counter-example and related components; both do the same job: steering the person away from this component.
The Information a Counter-Example Carries
The example shows the correct use of a component. The counter-example shows something else: the component’s boundary. Neither substitutes for the other.
A concrete example from the catalog interface: badge shows a record’s status —
“on the shelf,” “on loan,” “reserved.” notification-banner reports an event to the
user — “your loan was completed.” Both are small, colored, short-text boxes describing
a state. Only the counter-example puts the distinction into writing: a badge is a
property of an object and stays with it; a notification banner is the result of an
action and appears after it. This cannot be read from the prop table, since the two
components’ props are nearly identical.
The counter-example’s second job is closing the escape hatch. As shown in the
Component-Based Interface Development course, a component hands its caller an escape
hatch, and if the doc does not say where that hatch ends, every team draws its own
boundary. “Do not put a button inside a badge — use button for a clickable state” is
a rule, and it can be checked.
Computing Coverage
Tracking documentation as “done” or “not done” is not useful; docs are written partially and go stale partially. The program below takes the catalog’s nineteen documented components, computes coverage at the section and component level, sorts gaps by usage impact, and finally compares the prop table against the real interface.
// docs.mjs — component-level and section-level documentation coverage // A component doc's required sections. const SECTION = ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "accessibility-note", "related"]; // For each component, the sections it has and its usage count (same catalog as lesson 01). const DOCS = [ { name: "button", usage: 412, has: ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "accessibility-note", "related"] }, { name: "link", usage: 268, has: ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "accessibility-note", "related"] }, { name: "badge", usage: 188, has: ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "accessibility-note"] }, { name: "card", usage: 157, has: ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "accessibility-note", "related"] }, { name: "text-field", usage: 143, has: ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "accessibility-note", "related"] }, { name: "checkbox", usage: 96, has: ["purpose", "when-to-use", "prop-table", "example", "accessibility-note", "related"] }, { name: "search-field", usage: 71, has: ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "accessibility-note", "related"] }, { name: "notification-banner", usage: 63, has: ["purpose", "when-to-use", "prop-table", "accessibility-note"] }, { name: "dropdown", usage: 57, has: ["prop-table", "example"] }, { name: "empty-state", usage: 52, has: ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "accessibility-note", "related"] }, { name: "modal", usage: 45, has: ["purpose", "when-to-use", "prop-table", "accessibility-note"] }, { name: "pagination", usage: 41, has: ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "accessibility-note", "related"] }, { name: "skeleton", usage: 38, has: ["purpose", "when-to-use", "prop-table", "example"] }, { name: "radio-group", usage: 34, has: ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "related"] }, { name: "breadcrumb", usage: 29, has: ["purpose", "when-to-use", "when-not-to-use", "prop-table", "example", "accessibility-note", "related"] }, { name: "tabs", usage: 24, has: ["purpose", "when-to-use", "prop-table", "example"] }, { name: "accordion", usage: 17, has: ["purpose", "prop-table", "example"] }, { name: "tooltip", usage: 12, has: ["prop-table"] }, { name: "table", usage: 9, has: ["prop-table", "example"] }, ]; // Section-level coverage: which section is missing most often? console.log("section has doc component share usage share"); const totalUsage = DOCS.reduce((t, b) => t + b.usage, 0); for (const sec of SECTION) { const has = DOCS.filter((b) => b.has.includes(sec)); const k = has.reduce((t, b) => t + b.usage, 0); console.log( `${sec.padEnd(20)} ${String(has.length).padStart(6)}/${DOCS.length} ` + `${((has.length / DOCS.length) * 100).toFixed(1).padStart(15)}% ` + `${((k / totalUsage) * 100).toFixed(1).padStart(11)}%` ); } // Component-level coverage and gap impact. const rows = DOCS.map((b) => { const missing = SECTION.filter((sec) => !b.has.includes(sec)); return { ...b, missing, coverage: (SECTION.length - missing.length) / SECTION.length, impact: missing.length * b.usage }; }).sort((a, b) => b.impact - a.impact); console.log("\ncomponent coverage usage gap impact missing sections"); for (const r of rows) { if (r.missing.length === 0) continue; console.log( `${r.name.padEnd(21)} ${(r.coverage * 100).toFixed(0).padStart(7)}% ${String(r.usage).padStart(7)} ` + `${String(r.impact).padStart(12)} ${r.missing.join(", ")}` ); } const unweightedCoverage = rows.reduce((t, r) => t + r.coverage, 0) / rows.length; const weightedCoverage = rows.reduce((t, r) => t + r.coverage * r.usage, 0) / totalUsage; console.log(`\nunweighted coverage (component average) : ${(unweightedCoverage * 100).toFixed(1)}%`); console.log(`usage-weighted coverage : ${(weightedCoverage * 100).toFixed(1)}%`); // Comparing the prop table against the real interface. const INTERFACE = { button: ["type", "size", "state", "icon", "width"], dropdown: ["size", "state", "searchable", "multiple"], badge: ["tone", "size"], modal: ["size", "dismissible", "title"], }; const DOCUMENTED = { button: ["type", "size", "state", "icon"], dropdown: ["size", "state"], badge: ["tone", "size", "icon"], modal: ["size", "title"], }; console.log("\nprop table drift"); console.log("component undocumented documented but not in interface"); for (const name of Object.keys(INTERFACE)) { const missing = INTERFACE[name].filter((o) => !DOCUMENTED[name].includes(o)); const extra = DOCUMENTED[name].filter((o) => !INTERFACE[name].includes(o)); console.log(`${name.padEnd(11)} ${JSON.stringify(missing).padEnd(26)} ${JSON.stringify(extra)}`); } const totalProps = Object.values(INTERFACE).reduce((t, a) => t + a.length, 0); const documentedCount = Object.keys(INTERFACE).reduce((t, name) => t + INTERFACE[name].filter((o) => DOCUMENTED[name].includes(o)).length, 0); console.log(`prop table coverage: ${documentedCount}/${totalProps} (${((documentedCount / totalProps) * 100).toFixed(1)}%)`);
section has doc component share usage share purpose 16/19 84.2% 95.6% when-to-use 15/19 78.9% 94.6% when-not-to-use 10/19 52.6% 79.4% prop-table 19/19 100.0% 100.0% example 16/19 84.2% 93.2% accessibility-note 12/19 63.2% 89.1% related 10/19 52.6% 74.2% component coverage usage gap impact missing sections dropdown 29% 57 285 purpose, when-to-use, when-not-to-use, accessibility-note, related notification-banner 57% 63 189 when-not-to-use, example, related badge 86% 188 188 related modal 57% 45 135 when-not-to-use, example, related skeleton 57% 38 114 when-not-to-use, accessibility-note, related checkbox 86% 96 96 when-not-to-use tabs 57% 24 72 when-not-to-use, accessibility-note, related tooltip 14% 12 72 purpose, when-to-use, when-not-to-use, example, accessibility-note, related accordion 43% 17 68 when-to-use, when-not-to-use, accessibility-note, related table 29% 9 45 purpose, when-to-use, when-not-to-use, accessibility-note, related radio-group 86% 34 34 accessibility-note unweighted coverage (component average) : 73.7% usage-weighted coverage : 89.4% prop table drift component undocumented documented but not in interface button ["width"] [] dropdown ["searchable","multiple"] [] badge [] ["icon"] modal ["dismissible"] [] prop table coverage: 10/14 (71.4%)
What Section-Level Coverage Says
The first table shows which section is missing system-wide. The prop table is one hundred percent full, since it can be generated from the interface and usually is. The counter-example and related-components sections, by contrast, sit at 52.6%: only one component in two has them.
This distribution is not random: the section that can be auto-generated is full; the section that requires writing down a decision is empty. A documentation gap is often not a matter of time but of an undecided call — writing a counter-example requires the component’s boundary to already be decided, and it has not been.
The gap between the two columns is also meaningful: the counter-example exists for 52.6% of components but covers 79.4% of usage, so what is missing tends to be the less-used components. The related-components section shows a narrower gap — 52.6% against 74.2% — showing the gap also reaches heavily used components, and the second table confirms it.
Ranking the Gaps
The second table shows that coverage percentage and work priority differ. badge sits
at 86% coverage — one of the best-looking components in the list — but its gap impact
is 188, third-highest, because it is used in 188 places and its one gap touches
everyone. table, at 29% coverage, looks far worse, but its impact is only 45; it is
used in nine places.
The gap-impact product — missing section count times usage count — combines both into
a single ranking. dropdown, at the top of the ranking, was also seen in the previous
lesson: an undocumented candidate used in 57 places. When two separate audits point at
the same component, that component is next in line.
The 15.7-point gap between unweighted and weighted coverage is also worth reading: the
two answer different questions — unweighted asks “how much of the catalog is
documented,” weighted asks “how much of the usage lands on a documented component.”
Reporting only the weighted number hides low-usage components staying undocumented;
reporting only the unweighted number misses components like badge that have one gap
but wide reach. Both are reported together.
The Prop Table Going Stale
The last block shows documentation’s most insidious form of decay: the doc exists, but
it is wrong. button’s width prop and dropdown’s two props were added to the
interface but never written into the table — missing information: the person using
the component does not know the prop exists, writes their own workaround, and produces
drift.
The badge row carries a different defect: the icon prop is still in the doc but no
longer exists in the interface — wrong information, more expensive than missing
information. The person using it trusts the doc, passes the prop, nothing happens, and
they look for the bug in their own code.
This block also shows the direction of the fix: as long as the prop table is written by hand it will inevitably go stale; generated from the interface definition, drift becomes structurally impossible. The sections that must be written by hand — purpose, selection criterion, counter-example — are already the ones that cannot be generated. Documentation tooling follows this distinction: generate what can be generated, force what cannot to be written.
Summary
- Component documentation has seven sections, and each one answers a specific question the person using it asks.
- The counter-example carries boundary information the prop table cannot show; it is the only section that puts the distinction between two similar-looking components into writing.
- Sections that can be auto-generated stay full; sections that require writing down a decision stay empty — a documentation gap is often a decision gap.
- Coverage is reported both unweighted and usage-weighted together; one hides the neglect of low-usage components, the other hides a single gap in a widely used one.
- The product of missing-section count and usage produces a work ranking different from the coverage percentage.
- A prop documented but absent from the interface is more expensive than a prop present in the interface but undocumented; when the prop table is generated from the interface definition, this drift becomes structurally impossible.
Next Step
Comparing the doc’s prop table against the interface left a question open: adding a
width prop to button and removing the icon prop from badge do not carry the
same weight. The first breaks no usage; the second breaks every place that passes that
prop. The next lesson ties this distinction to the version number: it writes a script
that compares two versions’ component interfaces, classifies the changes, and computes
the next version number, then generates the migration guide from it.
To keep your progress and take notes, Log in
My notes
Log in to take notes.