Skip to content

Add Anchor Links to Headings

3 min read

This guide walks you through how to manually add anchor links to headings in your Starlight project. This is useful if you’re working with a version before v0.34 or want more control over the behavior and styling.

  1. Install the necessary dependencies using your package manager:

    Terminal window
    npm add rehype-autolink-headings rehype-slug hast-util-to-string hastscript html-escaper

    You may also need to add TypeScript definitions:

    Terminal window
    npm add --dev @types/hast @types/html-escaper
  2. Create a new file named rehype-autolink.ts in the plugins directory:

    plugins/rehype-autolink.ts
    // For more details, see: https://github.com/biomejs/website/blob/main/plugins/rehype-autolink.ts
    import 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]];
  3. 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"],
    }),
    ],
    });
  4. Create a headings.css file in src/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)));
    }
    }
  1. Bring back heading anchor links - withastro/docs
  2. Starlight v0.34 Release Notes - Astro