Heading links
CoreAutomatically adds slug-based id attributes and anchor links to every heading.
HeadingLinksPlugin is always active. It slugifies every <h2>–<h6>
and injects a self-referencing anchor link so readers can copy a deep-link
to any section.
Behaviour
For each heading the plugin:
Computes a GitHub-slugger-compatible slug from the heading text.
Deduplicates repeated slugs within the same document by appending a counter (
overview,overview-1,overview-2, …).Sets the
idattribute on the<h*>element.Appends an empty
<a href="#slug" class="hash-link" aria-label="…">as the heading's last child. The heading text is left unwrapped. The visible#glyph is rendered via CSS::afterso the anchor body stays empty, keeping heading-text extraction (e.g. for TOC) clean.
Example
## Introduction
## IntroductionProduces:
<h2 id="introduction">Introduction<a href="#introduction" class="hash-link" aria-label="Direct link to Introduction"></a></h2>
<h2 id="introduction-1">Introduction<a href="#introduction-1" class="hash-link" aria-label="Direct link to Introduction"></a></h2>Config
The plugin itself is always on. The ID strategy is configurable via
markdown.features.headingIds:
// zfb.config.ts
export default {
markdown: {
features: {
headingIds: { strategy: "hierarchical" },
},
},
};strategy: "flat" (default)
The behaviour described above: GitHub-slugger slugs with one dedup counter
shared across h2–h6. Omitting headingIds entirely keeps this scheme.
strategy: "hierarchical"
Each heading's ID is prefixed with its ancestor chain, joined by -:
## Foo
### Moo
#### MewProduces id="foo", id="foo-moo", id="foo-moo-mew" (instead of the
flat foo, moo, mew). The in-heading hash-link anchors, the
headings export, TOC export, and
link validation all follow the
same IDs.
Details:
A duplicated full path still gets the dedup counter (
a-b,a-b-1).A deduplicated parent contributes its final ID to children: the second
## Fooisfoo-1, so its### Barbecomesfoo-1-bar.Hierarchical anchors are reconstructible from the heading outline and collide far less often than flat slugs, at the cost of longer URLs.
Warning
Switching strategies is anchor-breaking: existing deep links to nested headings (#moo) stop resolving once IDs become #foo-moo.
Ordering note
HeadingLinksPlugin runs first in the hast phase. Plugins that depend
on stable heading id values — such as TocPlugin (opt-in
heading-marker-toc) and
TocExportPlugin (opt-in toc-export) —
must run after it.
See also
Heading-marker TOC — opt-in TOC insertion that reads the
idattributes this plugin produces.TOC export — opt-in structured TOC data export.