# MD051 - Link anchors should exist
Aliases: `link-fragments`
## What this rule does
Ensures that link anchors (the part after `#`) point to actual headings that exist. This includes both same-document anchors (like `#introduction`) and cross-file fragment links (like
`file.md#heading`) when linting multiple files together.
## Why this matters
- **Navigation**: Broken internal links frustrate readers trying to jump to sections
- **Maintenance**: Helps you catch links that break when headings are renamed
- **User experience**: Ensures smooth document navigation
## Examples
### ✅ Correct
```markdown
# Introduction
## Getting Started
See the [Introduction](#introduction) for background.
Jump to [Getting Started](#getting-started) to begin.
## Working with **bold** and *italic*
Link to [formatted heading](#working-with-bold-and-italic)
See [external documentation](README.md#setup) for setup instructions.
```
### ❌ Incorrect
```markdown
# Introduction
## Getting Started
[Jump to Installation](#installation) <!-- No "Installation" heading exists -->
[See Overview](#overview) <!-- No "Overview" heading exists -->
```
### 🔧 Fixed
This rule cannot automatically fix missing anchors - you need to either:
- Add the missing heading
- Update the link to point to an existing heading
- Remove the broken link
## Configuration
This rule supports configuring the anchor generation style to match different Markdown processors:
```toml
[MD051]
# Anchor generation style (default: "github")
# - "github": Preserves Unicode, underscores, and consecutive hyphens
# - "kramdown": ASCII-only with normalization, removes underscores
# - "kramdown-gfm" / "jekyll": Kramdown with GFM input (Jekyll/GitHub Pages)
# - "python-markdown" / "mkdocs": Python-Markdown style (collapses separators, ASCII-only)
anchor-style = "github"
```
When using `--flavor mkdocs`, the anchor style automatically defaults to `python-markdown` (unless explicitly overridden). This matches MkDocs's use of Python-Markdown's `toc` extension for anchor
generation.
### Anchor style differences
| `Hello World` | `#hello-world` | `#hello-world` | `#hello-world` |
| `respect_gitignore` | `#respect_gitignore` | `#respect_gitignore` | `#respectgitignore` |
| `The End - yay` | `#the-end---yay` | `#the-end-yay` | `#the-end---yay` |
| `CI/CD Migration` | `#cicd-migration` | `#cicd-migration` | `#cicd-migration` |
| `Café au Lait` | `#café-au-lait` | `#cafe-au-lait` | `#cafe-au-lait` |
| `你好世界` | `#你好世界` | (empty / `#_1`) | `#section` |
**Key differences**:
- **GitHub**: Preserves Unicode, underscores, and consecutive hyphens (e.g., `---`)
- **Python-Markdown** (MkDocs): ASCII-only, collapses consecutive separators (e.g., `---` → `-`), preserves underscores
- **kramdown**: ASCII-only, removes underscores, preserves consecutive hyphens
## Automatic fixes
This rule does not provide automatic fixes since it cannot guess which heading you meant to link to.
## How heading anchors work
When you create a heading like `## Getting Started`, Markdown automatically creates an anchor `#getting-started` that you can link to. The conversion follows these rules:
1. Convert to lowercase: `Getting Started` → `getting started`
2. Replace spaces with hyphens: `getting started` → `getting-started`
3. Remove special characters: `FAQ's & Tips!` → `faqs-tips`
4. Strip formatting: `**Bold** Text` → `bold-text`
## Learn more
- [CommonMark anchors](https://spec.commonmark.org/) - How link anchors work
- [GitHub heading IDs][github-ids] - GitHub's approach to heading anchors
[github-ids]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links
## Related rules
- [MD042](md042.md) - No empty links
- [MD034](md034.md) - URLs should be formatted as links
- [MD039](md039.md) - No spaces inside link text