How to Build a Table of Contents Component for Your Blog in React

If you‘ve ever read a long article or documentation page, you‘ve likely appreciated the convenience of a table of contents (TOC) component. A table of contents provides a high-level outline of the content, allowing readers to quickly navigate to specific sections of interest. This is especially valuable for lengthy blog posts or technical guides.

As a blogger, adding a table of contents to your articles can greatly enhance the user experience and make your content more accessible. In this comprehensive guide, we‘ll walk through the process of building a table of contents component for your blog using React.

Whether you‘re using Markdown, MDX, or another format for your blog posts, we‘ll explore techniques to programmatically extract the headings and generate a dynamic table of contents. Let‘s dive in!

Why Include a Table of Contents?

Before we get into the technical details, let‘s consider the benefits of incorporating a table of contents in your blog:

  1. Enhanced Navigation: A table of contents serves as a roadmap for your article, enabling readers to easily navigate to specific sections. This is particularly useful for long-form content where scrolling through the entire post may be time-consuming.

  2. Improved Scannability: By providing a concise overview of the article‘s structure, a table of contents allows readers to quickly scan the main points and decide which sections are most relevant to their interests.

  3. Increased Engagement: When readers can quickly find the information they‘re looking for, they‘re more likely to engage with your content and spend more time on your blog. A table of contents can help reduce bounce rates and improve user retention.

  4. Better Accessibility: For users with disabilities or those using assistive technologies, a table of contents provides an alternative way to navigate and understand the content structure.

  5. SEO Benefits: Search engines appreciate well-structured content, and a table of contents can help showcase the organization and hierarchy of your article. This can potentially lead to better search rankings and increased organic traffic.

Now that we understand the importance of a table of contents, let‘s explore how to build one in React.

Extracting Headings from Markdown or MDX

To generate a table of contents, we first need to extract the headings from our blog post‘s content. If you‘re using Markdown or MDX, you can leverage existing libraries to parse the content and retrieve the headings.

Here are a few popular options:

  1. rehype-extract-toc: This library uses rehype, a unified ecosystem for processing HTML, to extract the table of contents from a Markdown or MDX file.

  2. markdown-toc: As the name suggests, this library focuses specifically on generating a table of contents from Markdown content.

  3. extract-md-headings: This lightweight library extracts headings from a Markdown file and returns an array of objects containing the heading level, title, and slug.

For the purpose of this tutorial, we‘ll use the extract-md-headings library. First, install it via npm:

npm install extract-md-headings

Next, let‘s see how we can use it in our React component:

import React from ‘react‘;
import { extractHeadings } from ‘extract-md-headings‘;

const TableOfContents = ({ content }) => {
  const headings = extractHeadings(content);

  return (
    <div>
      <h2>Table of Contents</h2>
      <ul>
        {headings.map(({ level, title, slug }) => (
          <li key={slug}>
            <a href={`#${slug}`}>{title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TableOfContents;

In this example, we pass the content prop, which represents the Markdown or MDX content of our blog post, to the TableOfContents component. We then use the extractHeadings function to parse the content and retrieve an array of heading objects.

Each heading object contains the following properties:

  • level: The heading level (e.g., 1 for <h1>, 2 for <h2>, etc.)
  • title: The text content of the heading
  • slug: A URL-friendly slug generated from the heading title

We map over the headings array and render an unordered list (<ul>) with list items (<li>) for each heading. The title is displayed as a link (<a>) with an href attribute pointing to the corresponding heading‘s slug.

Styling the Table of Contents

With the basic structure in place, let‘s style our table of contents to make it visually appealing and user-friendly. Here‘s an example of how you can style the component using CSS:

.table-of-contents {
  background-color: #f8f8f8;
  padding: 20px;
  border-radius: 4px;
}

.table-of-contents h2 {
  font-size: 1.5rem;
  margin-bottom: 10px;
}

.table-of-contents ul {
  list-style-type: none;
  padding: 0;
}

.table-of-contents li {
  margin-bottom: 5px;
}

.table-of-contents a {
  color: #333;
  text-decoration: none;
}

.table-of-contents a:hover {
  text-decoration: underline;
}

In this CSS snippet, we apply some basic styles to the table of contents container, headings, unordered list, list items, and links. Feel free to customize the styles to match your blog‘s design aesthetic.

Handling Click Interactions

To enhance the user experience, we can add click functionality to the table of contents links. When a user clicks on a heading link, we want to smoothly scroll to the corresponding section in the blog post.

Here‘s how you can achieve this using the scrollIntoView method:

const handleClick = (event, slug) => {
  event.preventDefault();
  const element = document.getElementById(slug);
  element.scrollIntoView({ behavior: ‘smooth‘ });
};

// ...

<ul>
  {headings.map(({ level, title, slug }) => (
    <li key={slug}>
      <a href={`#${slug}`} onClick={(event) => handleClick(event, slug)}>
        {title}
      </a>
    </li>
  ))}
</ul>

In this updated code, we define a handleClick function that takes the event object and the slug as parameters. We prevent the default link behavior using event.preventDefault() and then use document.getElementById to find the corresponding heading element in the blog post.

Finally, we call element.scrollIntoView({ behavior: ‘smooth‘ }) to smoothly scroll to the selected heading.

Highlighting Active Heading

To provide visual feedback to users and indicate their current position within the blog post, we can highlight the active heading in the table of contents based on the user‘s scroll position.

Here‘s an example of how you can achieve this using the useEffect hook and the IntersectionObserver API:

import React, { useEffect, useState } from ‘react‘;

const TableOfContents = ({ content }) => {
  const headings = extractHeadings(content);
  const [activeId, setActiveId] = useState(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            setActiveId(entry.target.id);
          }
        });
      },
      { rootMargin: ‘0px 0px -80% 0px‘ }
    );

    headings.forEach(({ slug }) => {
      const element = document.getElementById(slug);
      if (element) {
        observer.observe(element);
      }
    });

    return () => {
      observer.disconnect();
    };
  }, [headings]);

  return (
    <div>
      <h2>Table of Contents</h2>
      <ul>
        {headings.map(({ level, title, slug }) => (
          <li key={slug} className={activeId === slug ? ‘active‘ : ‘‘}>
            <a href={`#${slug}`} onClick={(event) => handleClick(event, slug)}>
              {title}
            </a>
          </li>
        ))}
      </ul>
    </div>
  );
};

In this updated code, we introduce the activeId state variable to keep track of the currently active heading. We use the useEffect hook to set up an IntersectionObserver that observes the visibility of each heading element.

When a heading becomes visible on the screen (i.e., intersects with the viewport), the setActiveId function is called with the corresponding slug. This updates the activeId state and triggers a re-render of the component.

In the rendering logic, we conditionally apply the active CSS class to the list item based on whether its slug matches the activeId.

You can define the active class in your CSS to apply the desired styling to the active heading. For example:

.table-of-contents li.active a {
  font-weight: bold;
  color: #ff5a5f;
}

Additional Enhancements

Here are a few additional enhancements you can consider for your table of contents component:

  1. Nested Headings: If your blog posts have a hierarchical structure with nested headings (e.g., <h2>, <h3>, etc.), you can modify the component to display the headings in a nested format. You can use the level property of each heading object to determine the indentation level.

  2. Collapsible Sections: For extensive tables of contents, you can implement collapsible sections to allow users to expand or collapse certain parts of the TOC. This can be achieved by maintaining a state for each section‘s expanded/collapsed status and toggling it on click.

  3. Scroll Spy: Instead of relying solely on intersection observers, you can implement a scroll spy functionality that actively tracks the user‘s scroll position and updates the active heading accordingly. This can provide a smoother and more responsive experience.

  4. Accessibility: Ensure that your table of contents component is accessible to users with disabilities. Use appropriate ARIA attributes, such as role="navigation" and aria-label, to provide semantic meaning and context to assistive technologies.

  5. Responsive Design: Make sure your table of contents component is responsive and adapts well to different screen sizes. You can use CSS media queries to adjust the layout and styling based on the viewport width.

Conclusion

Congratulations! You now have a solid understanding of how to build a table of contents component for your blog using React. By extracting headings from your Markdown or MDX content, generating links, and implementing smooth scrolling and active heading highlighting, you can provide a user-friendly navigation experience for your readers.

Remember to style your component to match your blog‘s design, handle click interactions smoothly, and consider additional enhancements to further improve the functionality and accessibility of your table of contents.

By incorporating a well-designed table of contents, you can enhance the usability and engagement of your blog, making it easier for readers to explore and discover the valuable content you have to offer.

Happy coding and happy blogging!

Similar Posts