# 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](md024.md)** flags duplicate heading *text*. It misses distinct
  texts that slugify to the same anchor (`Setup & Run` vs `Setup Run`,
  `C++` vs `C`).
- **[MD051](md051.md)** 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. |

```toml
[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

```markdown
# Setup

## Configuration

## Usage
```

### Incorrect

```markdown
# Setup & Run

## Setup Run
```

Both headings slug to the same GitHub anchor, so `#setup--run` is ambiguous.

```markdown
# Intro

## Intro
```

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.

## Related rules

- [MD024 - Multiple headings with the same content](md024.md)
- [MD051 - Link fragments should reference valid headings](md051.md)
