A linked table of contents whose anchors match GitHub exactly
- [Getting Started](#getting-started) style — using GitHub's exact anchor rules: lowercase, punctuation and emoji stripped, spaces to dashes, and duplicate headings numbered -1, -2. Accented headings come out percent-encoded (#%C3%BCber-caf%C3%A9), exactly like the doctoc CLI emits. Copy the block into your README, or use Insert into document to place it between doctoc-compatible markers so it can be regenerated later. Everything runs locally.| Heading text | GitHub anchor | Rule at work |
|---|---|---|
Hello World | #hello-world | lowercase, spaces → dashes |
Getting Started (2nd occurrence) | #getting-started-1 | duplicates get -1, -2… |
C++ | #c | + is punctuation, stripped |
50% off | #50-off | % stripped, digits kept |
What's new? | #whats-new | apostrophes and ? stripped |
1. Introduction | #1-introduction | period stripped, spaces → dashes |
foo / bar | #foo--bar | slash stripped, both spaces dash |
A_B | #a_b | underscores survive |
Über café | #%C3%BCber-caf%C3%A9 | accents kept, URL-encoded in links |
😂 tada | #-tada | emoji stripped, its space still dashes |
The `config` object | #the-config-object | inline code backticks stripped |
Every row above is generated by the same github-slugger library this tool runs — the exact algorithm GitHub's renderer uses — not a hand-written approximation.
Two open-source pieces do the real work, both running in your browser. The heading scan follows the conventions of doctoc, the long-standing CLI for this job: it reads ATX headings (## Like this), setext headings (underlined with === or ---), skips anything inside fenced code blocks, and nests entries two spaces per level below the shallowest heading in range. Anchor generation uses github-slugger — the library built to replicate GitHub's slug algorithm — so every link points at the anchor GitHub will actually assign, including the duplicate-suffix behavior and URL-encoding.
Take a short README with the classic troublemakers:
# Project Docs · ## Getting Started · ### Install · a second ## Getting Started · ### Über café & co · ## 😂 tada
The generated TOC (title "Contents"):
- [Project Docs](#project-docs)
- [Getting Started](#getting-started)
- [Install](#install)
- [Getting Started](#getting-started-1)
- [Über café & co](#%C3%BCber-caf%C3%A9--co)
- [😂 tada](#-tada)
Four things to notice: the second "Getting Started" links to #getting-started-1, not #getting-started — link both naively and the second click lands on the first section. The accented heading's anchor is percent-encoded, matching what doctoc produces and what browsers expect. The emoji heading keeps its dash prefix. And nesting is two spaces per level, which renders cleanly as nested lists everywhere.
With markers on, the TOC ships wrapped in <!-- START doctoc generated TOC … --> and <!-- END … --> comments. Generate again with Insert into document and the tool replaces what's between the markers instead of stacking a second copy — the same contract the doctoc CLI uses, so files stay compatible whether you regenerate here or in a terminal.
<h2>…</h2> written by hand) aren't markdown headings; most scanners, this one included, skip them.Paste your markdown above, press Generate, and copy the TOC block to the top of your file (or use the insert button, which replaces any existing TOC between the markers). Each entry links to its heading with an anchor like #getting-started, which works on GitHub, GitLab, and most markdown renderers.
Lowercase everything, strip punctuation and emoji, and turn spaces into dashes. Punctuation like +, %, ?, and / disappears; accented letters (ü, é) stay; underscores stay. If the result collides with an earlier anchor on the page, GitHub appends -1, -2: two headings called Docs become #docs and #docs-1. This generator runs GitHub's own slug algorithm, so the links it emits are the anchors GitHub actually assigns.
Because every heading on a page must have a unique anchor. When two headings have the same text, the second one's anchor gets a numeric suffix — Getting Started appears twice becomes #getting-started and #getting-started-1 — and a TOC that links both to #getting-started sends both clicks to the first section. The generator tracks duplicates the same way GitHub does and emits the right suffix automatically.
The START/END HTML comments mark the generated region so it can be replaced wholesale later. The doctoc command-line tool uses exactly these markers; this generator is compatible with them — run it on a file that already has a marked TOC and it swaps in a fresh one instead of growing a second copy.
Accented and non-ASCII characters in anchors get URL-encoded in links — a heading like Über café produces #%C3%BCber-caf%C3%A9 -- co. That's correct and it's what doctoc emits: browsers decode it back to the characters when you click. Copy the link as-is; don't hand-decode it.
Mostly. GitLab, Bitbucket, VS Code's markdown preview, and most static site generators use the same or compatible anchor rules. Some platforms differ in edge cases (emoji, capitalization, or symbols), so if a link doesn't jump on your renderer, compare the anchor your renderer assigned (hover the heading's link icon) with what the TOC emits.