MD080 - Heading anchors must be unique¶
Aliases: heading-anchor-collision
Opt-in: disabled by default. Enable explicitly (e.g. add MD080 to your
config's enabled rules) because the collision is functional under platform
auto-suffixing and flagging it changes established lint output.
What this rule does¶
Flags two or more headings whose generated URL-safe anchor (slug) is identical.
The anchor is computed with the configured anchor style; an explicit
{#custom-id} takes precedence over the generated slug.
This is deliberately distinct from two neighbouring rules:
- MD024 flags duplicate heading text. It misses distinct
texts that slugify to the same anchor (
Setup & RunvsSetup Run,C++vsC). - MD051 flags broken fragment references. MD080 flags ambiguous fragment targets: the link resolves, but not unambiguously.
Why this matters¶
A [text](#slug) link, and the virtual-page identifier some viewers derive
from an H1/H2 title, can only resolve to the first heading that produced a
given slug. GitHub and MkDocs paper over the collision by auto-suffixing the
later anchor (slug-1), which is functional but surprising: a hand-written
#slug link that meant the second heading silently lands on the first.
Configuration¶
| Option | Type | Default | Description |
|---|---|---|---|
anchor-style |
string | github |
Slug algorithm: github, kramdown-gfm, kramdown, python-markdown. When unset, follows the active flavor. |
levels |
array of int | [1, 2, 3, 4, 5, 6] |
Heading levels whose anchors must be unique. Set to [1, 2] to check only page-identifier titles. |
[MD080]
# Slug algorithm: "github", "kramdown-gfm", "kramdown", or "python-markdown".
anchor-style = "github"
# Heading levels whose anchors must be unique. Use [1, 2] for page ids only.
levels = [1, 2, 3, 4, 5, 6]
Examples¶
Correct¶
Incorrect¶
Both headings slug to the same GitHub anchor, so #setup--run is ambiguous.
Same text at different levels still produces one shared #intro anchor.
Automatic fixes¶
None. Renaming a heading - and every link that targets it - is a semantic decision the linter must not make automatically.