DEV Community

Cover image for Adding a TOC in Astro
Chris Bongers
Chris Bongers

Posted on • Originally published at daily-dev-tips.com

Adding a TOC in Astro

A big part of Markdown is that it's great to write articles and not worry about the markup quickly.

But at the same time, that brings some limitations with it.
Limitations like how we can add a table of contents? (TOC)

This article will show you how to add one of those TOCs to your Astro-powered website.

Note: I'll be using Astro v0.23 for this article

Setting up the framework

Let's set up a basic framework to work with.

npm init astro -- --template blog
Enter fullscreen mode Exit fullscreen mode

This will set up a basic Astro blog started, visit the pages/post folder and modify the existing post to include a complete markdown structure with some headings.

Headings are created by using the # sign. (One for each heading)

# Heading 1
## Heading 2
### Heading 3
Enter fullscreen mode Exit fullscreen mode

Once you are happy with the blog post, run the website and see how it looks.

npm run dev
Enter fullscreen mode Exit fullscreen mode

You should have a very minimalistic blog with a detailed article by now.

Adding the TOC markdown plugin

Luckily, we won't have to create this plugin from scratch.
There are amazing rehype plugins already made that we can use.

First, let's install the plugins we need, which are:

  • rehype-autolink-headings
  • rehype-slug
  • rehype-toc

To install them run the following command:

npm i rehype-autolink-headings rehype-toc rehype-slug
Enter fullscreen mode Exit fullscreen mode

With those installed, we can tell Astro to start using these plugins.

Open up the astro.config.mjs file. This file handles all the things around the build of Astro.

The first thing we have to do is import the existing Astro remark rendered. This holds all of Astro's needed config.

import astroRemark from "@astrojs/markdown-remark";
Enter fullscreen mode Exit fullscreen mode

Then inside the export we need to add a new options for markdown, which will look like this:

export default /** @type {import('astro').AstroUserConfig} */ ({
    renderers: [],
    buildOptions: {
        site: 'https://example.com/',
    },
    markdownOptions: {
        render: [
            astroRemark,
            {
                rehypePlugins: [
                    "rehype-slug",
                    [
                        "rehype-autolink-headings",
                        { behavior: "append"},
                    ],
                    [
                        "rehype-toc",
                        { headings: ["h1", "h2"] }
                    ]
                ],
            },
        ],
    },
});
Enter fullscreen mode Exit fullscreen mode

As you can see, we added the markdownOptions, and inside we added the default astroRemark and then the plugins we want to use.

It's important to note the order of these plugins seems to make a difference, so start by adding the rehype-slug since the other two rely on that.

You can see the auto-link headings and TOC plugin come with a configuration object. You can modify or change this however you need.

When you re-run your website (it's crucial to re-run as this will only take effect then), you should see a super cool TOC that you can click and navigate from.

Adding a Table of contents in Astro

You can also find the complete code example on GitHub.

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Top comments (5)

Collapse
 
stivncastillo profile image
Stiven Castillo

That implementation is deprecated, I've implemented TOC as follows:

Install

npm i rehype-slug rehype-toc rehype-autolink-headings rehype-stringify remark-toc
Enter fullscreen mode Exit fullscreen mode

astro.config.mjs

import markdownConfig from './markdown.config'

export default defineConfig({
  markdown: markdownConfig,
  integrations: [
    mdx({
      ...markdownConfig,
      extendPlugins: false,
    }),
  ]
});
Enter fullscreen mode Exit fullscreen mode

markdown.config.ts

import remarkToc from "remark-toc";
import rehypeToc from "rehype-toc";

export default {
  remarkPlugins: [[remarkToc, { tight: true, ordered: true }]],
  rehypePlugins: [
    [
      rehypeToc,
      {
        headings: ["h1", "h2", "h3"],
        cssClasses: {
          toc: "toc-post", 
          link: "toc-link", 
        },
      },
    ],
  ],
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dailydevtips1 profile image
Chris Bongers

Thanks for the update Stiven,

Can't believe it's already deprecated ๐Ÿ‘€
If your up for it, I'd love to update the existing article, perhaps you can be of assistance there?

Collapse
 
stivncastillo profile image
Stiven Castillo

YW, I've already written a little post, if you want you could base on it dev.to/stivncastillo/how-to-implem...

Collapse
 
peerreynders profile image
peerreynders

Markdown - Layout

Alternately (much more bare bones) layouts receiving markdown content can obtain a content prop.

// src/layouts/BaseLayout.astro
const { content } = Astro.props;
Enter fullscreen mode Exit fullscreen mode

which includes astro.headers

{
  "astro": {
    "headers": [
      {
        "depth": 1,
        "text": "Astro 0.18 Release",
        "slug": "astro-018-release"
      },
      {
        "depth": 2,
        "text": "Responsive partial hydration",
        "slug": "responsive-partial-hydration"
      }
      /* ... */
    ],
    "source": "# Astro 0.18 Release\\nA little over a month ago, the first public beta [...]"
  },
  "url": ""
}
Enter fullscreen mode Exit fullscreen mode

The slug is the id on the h* tag (and consequently the hash fragment of its URL).

This is demonstrated in the docs example template starting at MainLayout.astro layout supplying the TableOfContents.tsx component with the headers.

Collapse
 
dailydevtips1 profile image
Chris Bongers

Yep their own fetchContent way is pretty solid as well!
This one might just be easier for most people, and a bit more dynamic from the start.