Create an automated Table of Contents with MDX
James A. Hernandez
Posted on May 21, 2024
Using GatsbyJS, I have a blog which has posts summarized with a Table of Contents. The TOC should automatically pull any H2s from the content and be self-generated. Like so:
This code will take all H2 tags and assign anchor tags and summarize as a Table of Contents, as with this post.
// blogTemplate.tsx
<MainLayout>
<Container fluid="sm" >
<Breadcrumb>
<Breadcrumb.Item href="/">Home</Breadcrumb.Item>
{frontmatter.status === 'portfolio' ? (
<Breadcrumb.Item href="/portfolio">Portfolio</Breadcrumb.Item>
) : frontmatter.status === 'key-segment' ? null : (
<Breadcrumb.Item href="/blog">Blog</Breadcrumb.Item>
)}
<Breadcrumb.Item active className="breadcrumb-truncate">
{frontmatter.title}
</Breadcrumb.Item>
</Breadcrumb>
<h1>{frontmatter.title}</h1>
<div>
<MDXProvider>{children}</MDXProvider>
</div>
</Container>
</MainLayout>
The first thing we would like to do is have automatic anchor tags placed on our H2 headings. We do this by taking advantage of MDX and creating a react component that will envelop our H2 in our MDX document.
This is the component:
// H2ThemeWrapper.tsx
function getAnchor(text: string) {
return text
.toLowerCase()
.replace(/[^a-z0-9 ]/g, '')
.replace(/[ ]/g, '-');
}
const H2ThemeWrapper = ({ children }) => {
const anchor = getAnchor(children);
const link = `#${anchor}`;
return (
<h2 className="remark__h2 mt-4" id={anchor}>
<a href={link}>ยง </a>
{children}
</h2>
);
};
export default H2ThemeWrapper;
And this is how it's used in our MDX document:
// index.md
---
date: '2023-10-08'
title: 'Create an automated Table of Contents with MDX'
tags: ['gatsby', 'react', 'markdown', 'mdx']
slug: table-of-contents-mdx
featuredImage: toc.jpg
author: James A. Hernandez
status: published
showTOC: true
prismThemes: ['prism-coldark-dark', 'prism-ghcolors']
description: "Within blog posts, I have a need to summarize the post by H2 tags, which I don't wish to do manually. This code will take all H2 tags and assign anchor tags and summarize as a Table of Contents, as with this post."
---
import H2ThemeWrapper from 'wrappers/H2ThemeWrapper';
<H2ThemeWrapper>Using an MDX Template</H2ThemeWrapper>
Creating the TOC
Once we have the anchor tags automated, now we move on to the task of creating the TOC. We'll create a useEffect in our template to pull all the H2s from Markdown (in this case all H2s using the class remark__h2
).
// blogTemplate.tsx
const [tableOfContents, setTableOfContents] = useState([]);
useEffect(() => {
const headings = Array.from(document.querySelectorAll('.remark__h2')).map(
(heading) => {
const anchor = heading.id;
const title = heading.textContent.replace('ยง', '').trim(); // Remove the initial link symbol
return { title, anchor };
},
);
setTableOfContents(headings);
}, []);
A separate component is used to stylize our TOC when it's created:
// TableOfContents.tsx
interface TableOfContentsProps {
tableOfContents: { title: string; anchor: string }[];
}
const TableOfContents: React.FC<TableOfContentsProps> = ({
tableOfContents,
}) => {
return (
<div className="tableOfContents mt-4 rounded">
<h2>Table of Contents</h2>
<ul>
{tableOfContents.map((item) => (
<li key={item.anchor}>
<a href={`#${item.anchor}`}>{item.title}</a>
</li>
))}
</ul>
</div>
);
};
export default TableOfContents;
And now our TOC can be inserted in the blog template, as such (in this case, conditional on a boolean if you so wish):
{frontmatter.showTOC && (
<TableOfContents tableOfContents={tableOfContents} />
)}
Posted on May 21, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.