Add Anchor Links to Headings in Starlight
3 min read
This guide explains how to add anchor links to headings in your Starlight project, making it easier for users to navigate and share specific sections.
Step-by-step Guide
Section titled Step-by-step Guide-
Install the necessary dependencies using your package manager:
Terminal window npm add rehype-autolink-headings rehype-slug hast-util-to-string hastscript html-escaperTerminal window pnpm add rehype-autolink-headings rehype-slug hast-util-to-string hastscript html-escaperTerminal window yarn add rehype-autolink-headings rehype-slug hast-util-to-string hastscript html-escaperTerminal window bun add rehype-autolink-headings rehype-slug hast-util-to-string hastscript html-escaperYou may also need to add TypeScript definitions:
Terminal window npm add --dev @types/hast @types/html-escaperTerminal window pnpm add --dev @types/hast @types/html-escaperTerminal window yarn add --dev @types/hast @types/html-escaperTerminal window bun add --dev @types/hast @types/html-escaper -
Create a new file named
rehype-autolink.ts
in theplugins
directory:plugins/rehype-autolink.ts // For more details, see: https://github.com/biomejs/website/blob/main/plugins/rehype-autolink.tsimport type { RehypePlugins } from "astro";import type { Element } from "hast";import { toString as hastToString } from "hast-util-to-string";import { h } from "hastscript";import { escape as htmlEscape } from "html-escaper";import rehypeAutolinkHeadings from "rehype-autolink-headings";const anchorLinkIcon = h("span",{ ariaHidden: "true", class: "anchor-icon" },h("svg",{ width: 16, height: 16, viewBox: "0 0 24 24" },h("path", {fill: "currentcolor",d: "m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z",}),),);const anchorLinkSRLabel = (text: string) =>h("span", { "is:raw": true, class: "sr-only" }, `Section titled ${htmlEscape(text)}`);const autolinkConfig = {properties: { class: "anchor-link" },behavior: "after",group: ({ tagName }: Partial<Element>) =>h("div", {tabIndex: -1,class: `heading-wrapper level-${tagName}`,}),content: (heading: Element) => [anchorLinkIcon,anchorLinkSRLabel(hastToString(heading)),],};export const rehypeAutolink = (): RehypePlugins => [[rehypeAutolinkHeadings, autolinkConfig],]; -
Import and configure the plugin in
astro.config.mjs
:astro.config.mjs import { rehypeAutolink } from "./plugins/rehype-autolink";import rehypeSlug from "rehype-slug";export default defineConfig({markdown: {rehypePlugins: [rehypeSlug, ...rehypeAutolink()],},integrations: [starlight({customCss: ["./src/styles/headings.css"],}),],}); -
Create a
headings.css
file insrc/styles/
and add the following styles:src/styles/headings.css .sl-markdown-content .heading-wrapper {--icon-size: 0.75em;--icon-spacing: 0.25em;line-height: var(--sl-line-height-headings);}.sl-markdown-content .level-h2 {font-size: var(--sl-text-h2);}.sl-markdown-content .level-h3 {font-size: var(--sl-text-h3);}.sl-markdown-content .level-h4 {font-size: var(--sl-text-h4);}.sl-markdown-content .level-h5 {font-size: var(--sl-text-h5);}.sl-markdown-content .anchor-link {margin-inline-start: calc(-1 * (var(--icon-size)));color: var(--sl-color-gray-3);opacity: 0;transition: opacity 0.2s ease;}.sl-markdown-content .heading-wrapper:hover > .anchor-link,.sl-markdown-content .anchor-link:focus {opacity: 1;}@media (min-width: 95em) {.sl-markdown-content .heading-wrapper {display: flex;flex-direction: row-reverse;justify-content: flex-end;gap: var(--icon-spacing);margin-inline-start: calc(-1 * (var(--icon-size) + var(--icon-spacing)));}}