Every long README deserves a table of contents, and every table of contents is a pile of anchor links waiting to break. The links look innocent — [Getting Started](#getting-started) — but they only work if they point at the anchor your renderer generated, and renderers generate anchors with their own rules: lowercase, punctuation stripped, duplicates suffixed. Here's the full rulebook with examples, the traps that break TOCs on real documents, and how to generate the whole block correctly instead of typing it.
A nested list of links, placed near the top of the file, each pointing at a heading's anchor:
## Contents
- [Project Docs](#project-docs)
- [Getting Started](#getting-started)
- [Install](#install)
Two spaces of indentation per level is the convention that renders as nested lists everywhere (four spaces can trigger code blocks in some parsers). The list is ordinary markdown — no special syntax — which is why a generator can produce it and why anything that produces wrong anchors is worse than nothing.
GitHub's algorithm — the one most tools copy — is a small pipeline:
-1, then -2, and so on.| Heading | Anchor | What happened |
|---|---|---|
Hello World | #hello-world | lowercase + dash |
What's new? | #whats-new | apostrophe and ? stripped |
foo / bar | #foo--bar | slash stripped, two spaces → two dashes |
C++ | #c | symbols gone |
Über café | #%C3%BCber-caf%C3%A9 | accents kept, URL-encoded in the link |
Getting Started (second one) | #getting-started-1 | duplicate suffix |
That percent-encoded row surprises people: the anchor itself is über-café, but links in markdown get URL-encoded, so the TOC entry carries #%C3%BCber-caf%C3%A9. Browsers decode it on click. Hand-write the raw characters and some renderers happily link them; others don't. Encoded is the portable form — it's what the doctoc CLI emits.
Because anchors must be unique per page. Two ## Getting Started headings produce #getting-started and #getting-started-1. A TOC that links both entries to #getting-started sends both clicks to the first section and silently strands the second one. This is the single most common TOC bug on real docs, because documentation loves repeated section names ("Configuration" in every chapter). The fix is mechanical — track duplicates the way the renderer does, in document order — and it's exactly what a generator does for you.
Convention: skip the H1 (there's one, it's the title, the reader is already there) and include H2–H3, sometimes H4. Depth is a readability decision: every level you add doubles the list and pushes content below the fold. For a 300-line README, H2–H3 usually lands between five and fifteen entries — the sweet spot. For API docs, H2–H4 often earns its length.
One subtlety worth knowing: a heading inside a fenced code block (```) is not a heading, and neither is a hand-written HTML <h2>. Scanners that miss the first case list code comments as sections; scanners that include the second produce anchors that don't exist in markdown-land.
Once a document grows, you'll edit headings and the TOC goes stale. The doctoc convention solves regeneration: wrap the TOC in HTML comments —
<!-- START doctoc generated TOC … -->
<!-- END doctoc generated TOC … -->
— and any doctoc-compatible tool (the CLI, or a browser generator) replaces exactly what's between them. The comments don't render, so readers never see them, and your TOC becomes a build artifact instead of a maintenance chore. Generate once with markers on, and refreshing after a restructure is one paste.
Runs GitHub's own slug algorithm — duplicates, emoji, accents handled — with doctoc-compatible markers and depth control.
Open the Markdown TOC GeneratorType it by hand when the document has five headings, none repeated, all plain ASCII — you'll be right, and it takes a minute. Generate when any of these hold: duplicate heading texts, accents or emoji in headings, more than a dozen headings, or a document that keeps changing. The Markdown TOC Generator runs the same slug library GitHub's ecosystem uses (github-slugger), in your browser, and emits doctoc-compatible markers — its output was verified byte-identical to the doctoc CLI's across duplicate, unicode, and emoji test documents.
While you're structuring a document: a markdown table generator spares you the pipe-counting for data tables, and once the file's finished, markdown to PDF turns it into something shareable outside the repo.