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.

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

How it works
rehype-slug
generatesid
s for each heading.rehype-autolink-headings
wraps them in links.hastscript
builds the icon element.- Tailwind’s
group-hover
fades the icon in.
Example configuration
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 fromhastscript
. 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 usegroup-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
bun add -D @iconify/tailwind4 @iconify-json/lucide
@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.
