Sometimes, there is a table of content to help us navigate through a blog more easily. When building a custom blog with remark and rehype, a standard solution is to use the rehype-toc
plugin.
In Astro, we can build a table of content without extra plugins. Astro already provides us with the necessary property by default. As an extra touch, we will add a scroll spy to keep track of our current heading.
I use the Astro blog starter template as a starting point.
Create the Component
Let’s create a new Astro component called TOC.astro
and define the needed props.
The pageHeadings
props will be special headings
props passed from the astro layout component. Astro automatically assigns an id to all headings in markdown, which become slug in headings
props.
Insert the TOC into the blog layout and pass it to the special headings
props
Let’s add a little style to the TOC, so it’s fixed to the right.
Here’s the result.
The TOC is working and sufficient for most use cases. But, we will enhance it with a scroll spy to highlight the active heading.
Scroll Spy with Intersection Observer
We must let the TOC know which heading is intersecting by observing/spying on the heading with Intersection Observer.
Add an Observer Callback Function
Firstly, insert a script tag in the TOC file. Afterward, create the observer callback function responsible for detecting and setting the active state.
The above code loops through each entry and selects the link element. It also has a guard in case the link element doesn’t exist, which is unlikely.
Then, add the active styling to the intersecting entry.
Here’s the active class for this example.
Observer Option
Let’s define the option for the observer.
Here’s the explanation for the option.
threshold: 1
means we want to register the element as an entry when the element is fully visible.rootMargin: "0px 0px -66%"
means we crop the observer’s viewport height by 66% at the bottom. So, our viewport have 33% of it’s height. It’s helpful because we want the entry to be active only when a user has scrolled enough past the heading.
Observe the Headings
We have all the pieces needed to create an observer instance to observe the headings.
What the code does is select all headings that we want to observe. Then, loop through each heading and observe them by calling observe()
.
Here’s the final result.