# all·markdown

How to add a table of contents in Markdown

Try it — edit the markdown and watch the preview

Result

Runs entirely in this tab. Open the full editor to work on a real document.

Markdown has no table-of-contents directive. A contents list is a list of ordinary links pointing at heading anchors:

- [Installing](#installing)
- [Configuration](#configuration)
- [Troubleshooting](#troubleshooting)

Every heading gets an anchor automatically. Nothing has to be declared.

How the anchor is generated

Take the heading text, then:

  1. lowercase it
  2. replace each run of spaces with a single hyphen
  3. remove punctuation
  4. leave everything else alone
HeadingAnchor
## Installing#installing
## Set-up (fast)#set-up-fast
## Two words here#two-words-here
## FAQ#faq

Two headings with the same text get -1 and -2 appended in document order.

This is where most broken anchor links come from: the brackets, the colon or the emoji in the heading is gone from the slug, and the guess does not match. Render the page and copy the link from the heading rather than working it out by hand.

Custom anchors

## Configuration {#config}

Works in kramdown (Jekyll), Pandoc, Python-Markdown and Quarto. GitHub renders the braces as literal text.

Where HTML is allowed, this works anywhere:

<a id="config"></a>
## Configuration

Empty anchor immediately before the heading. Ugly, portable.

What builds the list for you

GitHub shows a contents dropdown at the top-right of any rendered Markdown file. There is nothing to add to the file — it is built from the headings.

Static site generators — Hugo, Jekyll, Docusaurus, MkDocs — all have a built-in contents variable you place in the template rather than the document.

The editor on this page builds one live in the Contents panel while you type, and clicking an entry scrolls the preview to it. That is a reading aid rather than something inserted into the file, so the file stays clean.

If you want the list in the file — a README, where the reader may be looking at raw text — write it by hand and keep it short. A contents list longer than the screen is worse than none.

Linking between files

The same hash works after a path:

[the config section](docs/setup.md#configuration)

On GitHub this resolves relative to the current file. In a static site it resolves against the built URL, which may not be the same path — check one before writing fifty.

Common questions

Does Markdown generate a table of contents automatically?
No. Markdown has no directive for it — a contents list is ordinary links pointing at heading anchors. Some tools add one for you: GitHub shows a contents dropdown at the top of any rendered file, and most static site generators can build one.
How is a heading anchor generated?
Lowercase the heading text, replace each run of spaces with a hyphen, drop punctuation, and keep the rest. "Two words here" becomes `#two-words-here`. Duplicate headings get `-1`, `-2` appended in order.
How do I link to a section?
`[Configuration](#configuration)` — a normal link whose target is a hash followed by the anchor. Nothing else is needed; the anchor exists as soon as the heading does.
Why is my anchor link broken?
Usually punctuation or an emoji in the heading. `## Set-up (fast)` becomes `#set-up-fast`, not `#set-up-(fast)`. Render the page and copy the link from the heading itself rather than guessing the slug.
Can I set my own anchor?
On some renderers. kramdown and Pandoc accept `## Heading {#custom-id}`, and everywhere that allows HTML you can put `<a id="custom-id"></a>` immediately before the heading. GitHub supports neither, so it is the generated slug or nothing there.