How I Write Software Docs

How I write software docs

The principles I keep reaching for whenever I write docs for a dev tool or an app. Written down so I don't have to rediscover them on every project.

The full canonical version lives in docs/guides/writing-docs.md in the Mxr repo. This page is the vault hub — the principles distilled, with links to atomic concept notes for each one.

The anti-goal

No reader should finish a doc and think "I get what this is, but I don't know how to make it useful."

That's it. Every other rule I keep is a tactic in service of this. See Operationally Useful Docs for the deletion test that catches violations.

The principles

1. Every section ends with something runnable

For a CLI: a real mxr invocation that produces real output. For an app: a screenshot of the actual UI flow, with the relevant button labelled. Description without action is the failure mode.

If a section still teaches something useful after deleting every code block, the section is mislabeled. See Operationally Useful Docs.

2. Document every shipped surface

If it ships, it has a doc page. Every command, flag, config key, event type, keybinding, and public media gallery needs a stable home. The discipline only holds if it is mechanical. Make CI enforce everything the code can generate, then review hand-written context and media behavior separately. See Document Every Shipped Surface.

3. Recipes solve goals, not restate reference

Anything composable gets a recipe section. A real recipe = problem statement + smallest pipeline + output shape. Filler recipes restate --help; real ones solve a job. See A Recipe Solves a Goal.

4. One Diátaxis quadrant per page

Tutorial, how-to, reference, or explanation — pick one. Mode-mixing is a review-blocker. A quickstart that detours into architecture is broken; a reference page that opens with rationale is broken. See Diátaxis Quadrants.

5. Voice: command first, explanation second

Second person, imperative. Show before tell. No "click here," no "see above." Placeholders paired with real values. Inline code for code things. Tighter than article voice. See Command First, Explanation Second.

6. Generate reference from the source of truth

Hand-edited reference pages rot the moment a flag changes. Generate them from the code, snapshot them in CI, fail the build on drift. Authors keep the parts that take judgment (the "when to use it" line, the three to four examples); the generator handles the boring mechanical part. See Generated Docs as Drift Defense.

7. Mutations are documented dry-run first

Every mutating example shows --dry-run before --yes. Copy-paste habits absorb the discipline. The doc convention mirrors the runtime contract. See Mutations Documented Dry-Run First.

8. Lock the decisions that get relitigated

For docs aimed at maintainers and contributors, an explicit "locked decisions (do not relitigate)" section saves every reviewer the same conversation. Pair it with an "out of scope (do not implement)" list. Both are short, both are honest about why, both are revisitable when constraints actually change. See Locked Decisions in Docs.

9. For public packages, document the trust story

A package README is not just an API brochure. It should explain why the package exists, what external spec or behavior it follows, what it refuses to do, and how a user can verify the claims. A conformance corpus is documentation you can run. See Package Extraction Is Trust Building and Conformance Corpus as Contract.

10. Write durable fit, not opponent-shaped claims

Claims like "no maintained crate exists" or "everyone reimplements this" can be true and still age poorly. Public docs should lead with what the thing does, where it fits, what it refuses to own, and how to verify it. Ecosystem analysis belongs in planning docs unless it is clearly dated. See Durable Docs Avoid Opponent-Shaped Claims.

11. Name the version surface

Source version, registry latest, lockfile version, generated-doc version, and app-consumed version can all differ. Write which surface you mean before writing the number. See Version Truth Has Surfaces.

12. Smoke-test live provider surfaces

Provider catalogs, SDK type lists, and local model registries are documentation-adjacent surfaces, but they are not runtime proof. For model routing, payment providers, auth callbacks, and other live integrations, the doc should name the configured surface and give a smoke test that uses the same account and path as production. See Model Catalogs Need Runtime Smoke Tests.

How these apply to apps, not just CLIs

The principles port:

For apps and docs sites, the homepage is part of the doc, not a marketing exception. Above the fold it should answer what this thing is, how to start, and one real thing to try next. If the page spends that space on a vague hero and makes the first runnable step wait below the fold, the docs are still speaking in campaign mode.

The principles aren't CLI-specific. They're a discipline about respecting the reader's time and the doc's accuracy. CLIs are just where I had to write them down first because the surface is so wide.

Reviewer checklist

When reviewing a doc PR — or my own draft before opening one — I scan for these:

A PR that fails any of these isn't ready. Send it back, don't merge it with "we'll fix it later."

Anti-patterns I keep watching for

How this connects to my broader thinking

See also