Documenting Components: The Only Docs That Don't Rot

Every team I've watched try to document their components starts in the same place: a page in the wiki. Someone writes up the Button — here are the variants, here's a screenshot, here's a table of props — and for about three weeks it's great. Then someone adds a loading prop and doesn't update the page. Then the primary color changes and the screenshot is now a lie. A month in, the docs describe a component that no longer exists, and the whole team has quietly learned to not trust them. So they read the source instead, and the docs rot in place, occasionally misleading a new hire.
This isn't a discipline problem, and treating it as one — "we just need to be better about updating the docs" — is why it keeps happening. People aren't lazy; the docs are structurally doomed. The reason is simple and it's the whole point of this article: the documentation lives in a different place than the code it describes. Two sources of truth, updated by two separate acts of will, and one of them always falls behind. You've seen this exact shape before — it's the same two-sources-of-truth failure as the real-time cache, just applied to prose instead of state.
So the question isn't "how do we write better component docs." It's "how do we make docs that can't drift" — and the answer is to stop writing docs that sit next to the code and start generating them from the code itself.
Why the Wiki Always Loses
Sit with why the wiki page decays, because the mechanism matters. When you change a component, updating its separate documentation is a second, optional action. The code change ships value on its own; the doc update is extra work with no immediate payoff, easy to defer, easy to forget, and nothing breaks when you skip it. So under any deadline — which is always — the doc update is the first thing to go. Multiply that across a team and a year, and every hand-maintained doc trends toward wrong.
The insight that fixes it is to remove the second action entirely. If the documentation is derived from the code rather than maintained alongside it, then changing the code changes the docs, automatically, with no act of will required. The drift becomes impossible not because everyone got more disciplined, but because there's no longer a gap for drift to live in. This is the same instinct as generating types from a schema or migrations from a model: one source of truth, everything else derived. Documentation is just another thing that should be derived, not duplicated.
The Props Table Should Come From the Props
The most concrete version of this: that table of props everyone hand-writes and forgets to update? It should not be hand-written. Your component's props are already fully described — in its TypeScript types. That's a machine-readable specification of every prop, its type, whether it's required, sitting right there in the code.
interface ButtonProps { /** Visual style of the button. */ variant: 'primary' | 'secondary' | 'danger'; /** Shows a spinner and disables interaction. */ loading?: boolean; /** Called when the button is clicked. */ onClick: () => void; } export function Button({ variant, loading, onClick }: ButtonProps) { /* ... */ }
Everything a props table would contain is in that interface — the names, the exact allowed values of variant, which props are optional, and even the descriptions in the doc comments. Tooling like Storybook reads those types directly and generates the props table for you. Add a prop, the table gains a row. Change variant's options, the table updates. Delete a prop, its row disappears. You didn't maintain anything — the documentation is a view of the types, and the types can't be wrong because they're the code that actually runs. The single most rot-prone piece of component docs becomes the single most reliable one, purely by deriving it instead of writing it.
Living Examples Beat Screenshots
The other half of component docs is showing the thing in use, and here the screenshot is the villain. A screenshot is a photograph of the component at one moment in the past — it stops being accurate the instant anything changes, and it can't show a single interactive state. Hover, focus, loading, disabled, the error variant — a static image shows none of it and silently goes stale on all of it.
The fix is the same move again: don't take a picture of the component, render the real component. A story in Storybook is a live instance of the actual component, mounted with real props, that you can interact with:
import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button'; const meta: Meta<typeof Button> = { component: Button }; export default meta; export const Primary: StoryObj<typeof Button> = { args: { variant: 'primary', onClick: () => {} }, }; export const Loading: StoryObj<typeof Button> = { args: { variant: 'primary', loading: true, onClick: () => {} }, };
Those aren't images of a button — they're the button, running, with the exact props your app would pass. If the button changes, the stories change with it, because they are the component, not a snapshot of it. You can hover the real hover state, watch the real spinner, tab to the real focus ring. A screenshot documents what the component looked like once; a story documents what the component is right now, and stays honest by construction.
And there's a compounding benefit that ties back to the design conversation: a living component catalog is where designers and engineers actually agree on what exists. When the real Button, in all its states, is browsable in one place, "does this already exist?" has an answer you can see and click — which is the difference between reusing the component and rebuilding it slightly differently for the fifth time.
How I'd Approach It
Strip it to decisions:
- Separate docs always rot. It's not a discipline problem — it's two sources of truth. Stop maintaining docs next to the code.
- Derive, don't duplicate. Generate documentation from the code so changing the code changes the docs, automatically.
- The props table comes from the types. Your TypeScript interface already is the spec; let tooling render it. Never hand-write a props table.
- Render real components, not screenshots. A live story stays accurate and shows interactive states; an image is stale the moment you save it.
- A living catalog drives reuse. When every component is browsable in all its states, "does this exist?" becomes answerable — and things get reused instead of rebuilt.
The reason component docs have such a bad reputation is that almost everyone builds them to fail, then blames themselves when they do. A wiki page and a screenshot are drift waiting to happen, and no amount of resolve fixes a structural flaw. The shift that makes documentation actually last is to stop treating it as a thing you write and start treating it as a thing you derive — from the types, from the real components, from the code that's already the truth. Do that, and your docs stop being a chore you fall behind on and become a side effect of building the components at all. That's the only kind of documentation I've seen survive contact with a deadline.
This is where the "build a real app" spine of the series ends — from starting a project all the way through documenting what you built. The remaining articles step outside the browser entirely: next up, React on the desktop — when Electron or Tauri actually earns its place, and what changes when your React app is a window instead of a tab.
If your team has component docs that nobody trusts, it's almost certainly the separate-source-of-truth problem, and it's fixable without a documentation crusade. Tell me how your docs are set up and I'll tell you which piece to derive instead of write.