Skip to content
academia.sh

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.

Start typing to search.

↑↓ Esc navigate · open · close