GitHub-Style Heading Links in Astro

Add GitHub-style hover link icons to Astro Markdown headings using rehype-autolink-headings, hastscript, and Tailwind CSS for better navigation and shareable sections.

3 min read View on GitHub

Overview

I wanted the headings in my Astro Markdown site to behave like GitHub’s:
hover to reveal a link icon for deep-linking to that heading.

After reading rehype-autolink-headings’ docs on using hastscript,
I realized I could replicate the effect with some tailwindcss magic.


Before and after

Here’s how it looked before and after adding the configuration.

Without any changes, hovering over a heading does nothing. There’s no visual cue that it’s linkable.

Screenshot of Astro heading without hover link

Now when you hover over a heading, a subtle link icon appears.
Clicking it jumps directly to that heading’s URL.

Screenshot of Astro heading with hover link icon

How it works


Example configuration

astro.config.ts
import { defineConfig } from "astro/config";
import { h } from "hastscript";
import rehypeSlug from "rehype-slug";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
const autoLinkHeadingOpts = {
behavior: "wrap",
content: [
h("span", {
"aria-hidden": "true",
class:
"icon-[lucide--link] absolute left-0 top-1/2 -translate-y-1/2 " +
"opacity-0 group-hover:opacity-100 transition-opacity duration-150 " +
"text-base-content/50 h-[1em] w-[1em]",
}),
],
properties: {
className: "group relative block pl-[1.5em] -ml-[1.5em] link-hover",
tabindex: "-1",
},
};
export default defineConfig({
markdown: {
gfm: true,
rehypePlugins: [rehypeSlug, [rehypeAutolinkHeadings, autoLinkHeadingOpts]],
},
});

Breaking down the configuration

The autoLinkHeadingOpts object controls how rehype-autolink-headings wraps and styles your headings.

content

content: [
h("span", {
"aria-hidden": "true",
class:
"icon-[lucide--link] absolute left-0 top-1/2 -translate-y-1/2 " +
"opacity-0 group-hover:opacity-100 transition-opacity duration-150 " +
"text-base-content/50 h-[1em] w-[1em]",
}),
],
  • h comes from hastscript. It creates a <span> element in hast syntax, which is how rehype represents HTML.
  • aria-hidden="true" hides the icon from screen readers since it’s decorative.
  • icon-[lucide--link] is the Iconify class for the link icon.
  • absolute left-0 top-1/2 -translate-y-1/2 positions the icon to the left of the heading and vertically centers it.
  • opacity-0 group-hover:opacity-100 transition-opacity duration-150 hides the icon until you hover over the heading, then fades it in smoothly.
  • text-base-content/50 makes the icon a muted, theme-aware color. In this case daisyUI is being used.
  • h-[1em] w-[1em] sizes the icon to match the heading’s font size.

properties

properties: {
className: "group relative block pl-[1.5em] -ml-[1.5em] link-hover",
tabindex: "-1",
},
  • group lets us use group-hover inside the icon’s classes.
  • relative makes the heading container the positioning reference for the absolutely positioned icon.
  • block ensures consistent spacing and padding behavior.
  • pl-[1.5em] -ml-[1.5em] adds space for the icon while keeping the text aligned with other elements.
  • link-hover applies link hover styles from your Tailwind theme.
  • tabindex="-1" removes the link from the tab order so keyboard navigation focuses on the heading text instead.

Note on icons

The icon-[lucide--link] class comes from Iconify with the Tailwind 4 plugin.

Installing the plugin

Terminal window
bun add -D @iconify/tailwind4 @iconify-json/lucide
globals.css
@plugin "@iconify/tailwind4";

Using a different icon

Swap lucide--link for any other Lucide icon or from another installed Iconify collection. Or, use your own icon setup such as SVGs, React components, or any approach you prefer.


Wrapping up

With just a couple of rehype plugins, a sprinkle of hastscript, and some Tailwind magic, you can make your Astro headings more interactive, just like GitHub’s.

Gif of Astro heading with hover link icon

Thank you for reading ❤️.

Back to all posts