{"id":"l-ds-3-4","courseId":"design-systems","lang":"en","kind":"lesson","summary":"Structure component documentation around how people actually read it, generate the parts that drift, and write the guidance only humans can give, such as when not to use a component.","html":"<p>Northwind’s first documentation site had 140 pages, a beautiful typeface and a principles section that took a month to write. Our analytics told a humbling story: on component pages, the median visit lasted 38 seconds. People landed, scrolled to the first code block, copied it, and left. The principles section averaged nine visits a week, most of them from our own team.</p>\n<p>That’s not a failure of the readers. Engineers and designers open documentation in the middle of a task with one question: “Is this the right component, and how do I use it?” Good documentation answers that in the first screen and keeps everything else one scroll away.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Anatomy of a component page</h2>\n<p>Every Northwind component page follows the same order, so people learn where to look:</p>\n<ol>\n<li><strong>One-line description</strong> of what the component is for.</li>\n<li><strong>When to use, when not to use</strong>, with links to the alternative. “Use Dialog for decisions that block the current task. For non-blocking messages, use Toast.”</li>\n<li><strong>The primary example</strong>: a live demo of the most common correct usage, with copyable code.</li>\n<li><strong>Examples</strong> of each variant and important state, each with its own code.</li>\n<li><strong>Accessibility</strong>: keyboard behavior, what screen readers announce, and a split between what the component handles and what the team must still do (for example, “you must give every Dialog a <code dir=\"ltr\">Dialog.Title</code>”).</li>\n<li><strong>Content guidelines</strong>: how to write labels and messages. “Button labels start with a verb: <em>Assign driver</em>, not <em>Driver assignment</em>.”</li>\n<li><strong>Props table</strong>, generated from code.</li>\n<li><strong>Status and history</strong>: stable, beta or deprecated; the version it was added; links to the changelog.</li>\n</ol>\n<p>The order follows what people need under time pressure. The decision (“should I use this?”) and the copyable example come first; reference material comes last.</p>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Why this matters</span></p>\n<p>The first example on a page gets copied more than everything else combined, so it decides what ships. Northwind’s old Button page opened with a primary button. Production soon had pages with four primary buttons. When we changed the first example to a secondary and a primary button side by side, new code started following the one-primary-per-view rule without anybody being told.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">Generate what drifts, write what can’t be generated</h2>\n<p>Two parts of a docs page go stale fastest: props tables and code examples. Both can come from code. Props tables are generated from TypeScript types. Examples come from stories, the same files used for development and visual tests. In Storybook, a story file with the <code dir=\"ltr\">autodocs</code> tag gets a generated docs page with a live preview, the story’s code, and a controls table built from the component’s props:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Button.stories.tsx</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { Meta, StoryObj } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '@storybook/react-vite'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { Button } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> './Button'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> meta</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  component: Button,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  tags: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'autodocs'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">],</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  args: { children: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Assign driver'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">} </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">satisfies</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Meta</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">typeof</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Button>;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> default</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> meta;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Story</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> StoryObj</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">typeof</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> meta>;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Secondary</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Story</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {};</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Primary</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Story</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { args: { variant: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'primary'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } };</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Danger</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Story</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { args: { variant: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'danger'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, children: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Delete route'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } };</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Small</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Story</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { args: { size: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'sm'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } };</span></span></code></pre></div>\n<p>What code can’t generate is judgment: when to use the component, when not to, how to word its content, and why the system made the choices it did. That’s where the docs team’s writing time goes.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Documenting how, never when</span></p>\n<p>A page that lists every prop but never says when to choose the component produces exactly the misuse you were trying to prevent. Northwind’s Danger button was documented perfectly, prop by prop, and used for “Remove filter” on nine screens. One “when not to use” line with a do-and-don’t image fixed it within a release.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Docs are part of done</h2>\n<p>Documentation that ships later doesn’t ship. Northwind’s pull request template for system components has a checklist: stories for every variant and state, an updated docs page, an accessibility section, and a changelog entry. A component change without them doesn’t merge. Because the docs live in the same repository as the components, they’re reviewed in the same pull request by the same people.</p>\n<p>Then treat the docs like a product. Northwind tracks three signals: search queries that return no results, the questions asked in the design system Slack channel, and pages with high exit rates right after the “when to use” section. Each repeated question becomes a docs change. If the same question arrives three times, the answer belongs on the page, not in a thread.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Write the “when not to use” section first</span></p>\n<p>When documenting a new component, start with the cases where it is the wrong choice. It forces you to name the neighboring components and their boundaries, which is the information people are missing when they misuse something.</p>\n</aside>\n<p>In the exercise, you plan the documentation fixes for a real misuse. That completes the components section; next, you connect the design side and the code side of the system so they stay in sync.</p>\n","toc":[{"id":"sec-1","text":"Anatomy of a component page","level":2},{"id":"sec-2","text":"Generate what drifts, write what can’t be generated","level":2},{"id":"sec-3","text":"Docs are part of done","level":2}],"takeaways":["People scan documentation for a copyable example and a yes-or-no answer to “should I use this?”, so put those first.","Every component page needs “when to use” and “when not to use”, with links to the right alternative.","Generate props tables and examples from code and stories so they can’t drift; write by hand only what code can’t express.","Documentation is part of the definition of done and ships in the same pull request as the component change."],"further":[{"title":"Writing docs with Autodocs (Storybook, official repository docs)","url":"https://github.com/storybookjs/storybook/blob/next/docs/writing-docs/autodocs.mdx"},{"title":"How Users Read on the Web (Nielsen Norman Group)","url":"https://www.nngroup.com/articles/how-users-read-on-the-web/"},{"title":"Button component page (U.S. Web Design System)","url":"https://designsystem.digital.gov/components/button/"}],"quiz":[{"q":"<p>Analytics show most visitors to Northwind’s Button page leave within 40 seconds after copying the first code example. What should you conclude?</p>","options":[{"text":"The page is failing, and people should be made to read the whole page.","why":"Quick visits are often success: they found the example they needed. Forcing reading fights how people work."},{"text":"The first example should be the most common correct usage, with the most important guidance right next to it.","why":"Correct. If the first example is what gets copied, it decides what ships, so it must model the right default."},{"text":"The code examples should be removed so people read the guidelines.","why":"Removing what people need most sends them to copy from other products’ code instead, mistakes included."},{"text":"The page needs more variants in the first example.","why":"A busy first example is harder to copy correctly; show the common case first and the rest below."}],"answer":1},{"q":"<p>Teams keep using the Danger button for “Remove filter”, which isn’t destructive. Which documentation change helps most?</p>","options":[{"text":"A longer description of the Danger button’s colors.","why":"Color details don’t tell anyone when the variant is appropriate."},{"text":"A props table entry for <code dir=\"ltr\">variant=&quot;danger&quot;</code>.","why":"The props table shows the option exists; it says nothing about when to choose it."},{"text":"A “when not to use” note with a do-and-don’t example, pointing to the secondary variant for reversible actions.","why":"Correct. It names the exact misuse and gives the alternative at the moment of decision."}],"answer":2},{"q":"<p>The Button props table in the docs lists a <code dir=\"ltr\">kind</code> prop that was renamed to <code dir=\"ltr\">variant</code> two releases ago. What’s the durable fix?</p>","options":[{"text":"Update the table by hand and add a reminder to the release checklist.","why":"Checklists help, but hand-maintained tables will drift again on the next rename."},{"text":"Remove the props table and link to the source code.","why":"Many readers aren’t comfortable reading component source, and the table is useful when it’s correct."},{"text":"Review all docs pages once a quarter.","why":"A quarterly review finds drift months after it misled people."},{"text":"Generate the props table from the component’s TypeScript types during the docs build.","why":"Correct. A generated table changes when the code changes, so it can’t fall behind."}],"answer":3}],"exercise":{"kind":"choice","multiple":true,"options":["when-not","props-detail","first-example","principles","do-dont","slack"],"correct":["when-not","first-example","do-dont"],"title":"Fix the docs behind a misuse","prompt":"<p>Dispatchers complain that the app keeps interrupting them: six teams now open a blocking Dialog that says “Route saved!” after every save. You check the Dialog docs page. It opens with a live demo of exactly that success dialog, followed by a complete props table. There is no guidance on when to use Dialog versus Toast. Which changes would you make? Select all that apply.</p>\n","hints":["Remember which part of a page gets copied the most.","Good fixes act at the moment someone decides which component to use, inside the docs page itself."],"explanation":"<p>The first example is the most-copied code on the page, so it was teaching the misuse; replacing it with a real blocking decision, like “Delete route?”, changes what gets copied. A “when not to use” line and a do-and-don’t pair name the exact mistake and point to Toast at the moment of decision. More props detail, a principles link, or answering teams one by one in Slack leave the page itself unchanged, so the next team repeats the mistake.</p>\n","checkLabels":{},"optionText":{"when-not":{"text":"Add “When not to use: confirmations that don’t need a decision. Use Toast instead.” near the top of the page.","why":"Correct. It names the misuse and the alternative where people decide."},"props-detail":{"text":"Expand the props table with more detail on the <code dir=\"ltr\">open</code> and <code dir=\"ltr\">onOpenChange</code> props.","why":"The props work fine; the teams chose the wrong component, which the props table can’t address."},"first-example":{"text":"Replace the first example with a blocking decision such as “Delete route?” with Cancel and Delete actions.","why":"Correct. The first example is what gets copied, so it must model the right use."},"principles":{"text":"Add a link to the design principles page at the top of the Dialog page.","why":"Principles are general; nobody follows a link to find out whether a save message should block the screen."},"do-dont":{"text":"Add a do-and-don’t pair: Dialog for “Delete route?”, Toast for “Route saved”.","why":"Correct. Side-by-side examples make the boundary between the two components obvious at a glance."},"slack":{"text":"Reply to each of the six teams in Slack explaining the problem.","why":"It helps those six teams once, but the page still teaches the next team the same mistake."}}},"words":740,"readMinutes":4}