interactive table of contents for jekyll

Tables of Contents (TOCs) are essential for improving navigation on long documentation pages. An interactive TOC enhances user experience by allowing dynamic collapsing, highlighting current sections, and smooth scrolling. This article walks you through creating an interactive TOC for Jekyll documentation using Liquid and JavaScript.

Why Use an Interactive TOC?

  • Improves user navigation on lengthy documents
  • Highlights the reader’s current section
  • Collapsible nested items save screen space
  • Supports deep linking to specific sections

Generating TOC Data with Liquid

Jekyll’s Liquid templates can parse page content headings to build a TOC structure automatically. However, default Jekyll doesn't provide a direct method to extract headings from Markdown. We typically create the TOC manually or use plugins.

Manual TOC Data with YAML Front Matter

One approach is to define the TOC structure in front matter:

---
title: "Sample Doc"
toc:
  - title: Introduction
    id: introduction
  - title: Installation
    id: installation
    children:
      - title: Requirements
        id: requirements
      - title: Steps
        id: steps
  - title: Usage
    id: usage
---

This lets you build nested TOC items easily.

Rendering the TOC

Use a recursive Liquid include to render the TOC list:

{% raw %}
    {% for item in page.toc %}
  • {{ item.title }} {% if item.children %} {% include toc_recursive.html items=item.children %} {% endif %}
  • {% endfor %}
{% endraw %}

Creating the Recursive TOC Include

Create _includes/toc_recursive.html:

{% raw %}
    {% for item in include.items %}
  • {{ item.title }} {% if item.children %} {% include toc_recursive.html items=item.children %} {% endif %}
  • {% endfor %}
{% endraw %}

Styling the TOC

Basic CSS to make the TOC nested and collapsible:

ul.toc-list, ul.toc-sublist {
  list-style-type: none;
  padding-left: 1em;
}

ul.toc-sublist {
  display: none;
  margin-left: 1em;
}

li.active > ul.toc-sublist {
  display: block;
}

a {
  text-decoration: none;
  color: #0366d6;
}

a.active {
  font-weight: bold;
  color: #005cc5;
}

Adding JavaScript for Interactivity

This script enables toggling nested TOC items and highlights the current section based on scroll position:

document.addEventListener('DOMContentLoaded', () => {
  const toc = document.querySelector('.toc-list');
  if (!toc) return;

  // Toggle sublist visibility
  toc.querySelectorAll('li > a').forEach(anchor => {
    anchor.addEventListener('click', e => {
      const sublist = anchor.parentElement.querySelector('ul.toc-sublist');
      if (sublist) {
        e.preventDefault();
        sublist.style.display = sublist.style.display === 'block' ? 'none' : 'block';
      }
    });
  });

  // Highlight current section on scroll
  const sections = Array.from(document.querySelectorAll('h2, h3, h4')).filter(
    el => el.id
  );

  function onScroll() {
    const scrollPos = window.scrollY || window.pageYOffset;
    let currentId = '';
    for (const section of sections) {
      if (section.offsetTop <= scrollPos + 100) {
        currentId = section.id;
      }
    }
    toc.querySelectorAll('a').forEach(link => {
      link.classList.toggle('active', link.getAttribute('href') === '#' + currentId);
      const li = link.parentElement;
      li.classList.toggle('active', link.classList.contains('active'));
    });
  }

  window.addEventListener('scroll', onScroll);
  onScroll(); // initial call
});

Linking TOC to Document Headings

Make sure headings have corresponding id attributes matching TOC entries, for example:

<h2 id="installation">Installation</h2>
<h3 id="requirements">Requirements</h3>

SEO and Accessibility Considerations

  • Use semantic HTML lists
  • Ensure keyboard navigation by styling focus states
  • Add ARIA attributes if needed for better screen reader support

Enhancing User Experience

  • Add smooth scrolling behavior with CSS scroll-behavior: smooth; or JS
  • Remember collapsed state using localStorage for returning users
  • Highlight top-level TOC items on page load

Summary

Building an interactive TOC in Jekyll involves combining Liquid’s templating power with JavaScript for interactivity. By organizing TOC data manually or via automation, you can offer your users a seamless way to navigate even the most complex documentation pages.

Next Steps

  • Automate TOC generation from Markdown headings
  • Integrate search with TOC for faster navigation
  • Customize TOC style to fit your brand and theme