How to add anchor links to headings in Astro

Picture

Anchor links are a useful feature for websites that focus mainly on text, such as a project documentation site or a blog. Like when I changed the code blocks style of my site, I went back to the Astro Docs repo to figure out how to add their anchor links to my site, so let me show you what I did to pull it off!

Steps

  1. Open your terminal or command prompt and run the following command to navigate to the root of your Astro project:

    cd /path/to/your/project
  2. Add rehype-autolink-headings, html-escaper, hast-util-to-string and rehype-slug packages to your site's dependencies:

    npm install rehype-autolink-headings
    npm install html-escaper
    npm install @types/html-escaper
    npm install hast-util-to-string
    npm install rehype-slug
  3. Create the folder plugins in the root.

    mkdir plugins
  4. Create the file rehype-autolink-config.ts inside the plugins folder:

    # When using Windows
    cd . > plugins/rehype-autolink-config.ts
     
    # When using Linux or macOS
    touch plugins/rehype-autolink-config.ts
  5. Add the following code to the plugins/rehype-autolink-config.ts file:

    import { toString } from 'hast-util-to-string';
    import { h } from 'hastscript';
    import { escape } from 'html-escaper';
    import type { Options } from 'rehype-autolink-headings';
     
    const AnchorLinkIcon = h(
    	'span',
    	{ ariaHidden: 'true', class: 'anchor-icon' },
    	h(
    		'svg',
    		{
    			width: 16,
    			height: 16,
    			version: 1.1,
    			viewBox: '0 0 16 16',
    			xlmns: 'http://www.w3.org/2000/svg',
    		},
    		h('path', {
    			fillRule: 'evenodd',
    			fill: 'currentcolor',
    			d: 'M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z',
    		})
    	)
    );
     
    const createSROnlyLabel = (text: string) => 
    	h(
    		'span',
    		{ 'is:raw': true, class: 'sr-only' },
    		`${escape(text)}`
    	);
     
    export const autolinkConfig: Options = {
    	properties: { class: 'anchor-link' },
    	behavior: 'append',
    	content: (heading) => [AnchorLinkIcon, createSROnlyLabel(toString(heading))],
    };

    You can change the position of the anchor link icon by changing the value of behavior to one of these:

    • prepend: The anchor link <a> and its associated elements are placed before the content of the heading, meaning that the anchor link is displayed visually as part of the heading.

      <h2 id="steps">
        <a class="anchor-link" href="#steps">
          <span aria-hidden="true" class="anchor-icon"><svg ... ></svg></span>
          <span is:raw="true" class="sr-only">Steps</span>
        </a>
        Steps
      </h2>
    • append: The anchor link <a> and its associated elements are placed after the content of the heading, meaning that the anchor link is displayed visually as part of the heading.

      <h2 id="steps">
        Steps
        <a class="anchor-link" href="#steps">
          <span aria-hidden="true" class="anchor-icon"><svg ... ></svg></span>
          <span is:raw="true" class="sr-only">Steps</span>
        </a>
      </h2>
    • before: The anchor link <a> and its associated elements appear before the heading element.

      <a class="anchor-link" href="#steps">
        <span aria-hidden="true" class="anchor-icon"><svg ... ></svg></span>
        <span is:raw="true" class="sr-only">Steps</span>
      </a>
      <h2 id="steps">Steps</h2>
    • after The anchor link <a> and its associated elements appear after the heading element.

      <h2 id="steps">Steps</h2>
      <a class="anchor-link" href="#steps">
        <span aria-hidden="true" class="anchor-icon"><svg ... ></svg></span>
        <span is:raw="true" class="sr-only">Steps</span>
      </a>
  6. In the .css file containing your site's styles, add the following code:

    /* ===== anchor link ===== */
    .anchor-link {
    	@apply ml-2 text-skin-accent;
    }
    .anchor-link:hover {
    	@apply text-skin-base;
    }

    Note that I'm using Tailwind and that the text color is defined in my tailwind.config.cjs file. You can use any color you like, or even remove the ::hover selector if you don't want the anchor icon to change on hover.

    A plain CSS example would be:

    /* ===== anchor link ===== */
    .anchor-link {
    	margin-left: 0.5 rem;
    	color: rgb(255, 107, 1);
    }
  7. Add the following code to the astro.config.*:

    import rehypeSlug from "rehype-slug";
    import rehypeAutolinkHeadings from "rehype-autolink-headings";
    import { autolinkConfig } from "./plugins/rehype-autolink-config";
     
    export default defineConfig({
    	markdown: {
    		rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, autolinkConfig]],
    		...
    	},
    	...
    })

That's it!