Table of Contents
Automatic table of contents generation from page headings with scroll spy and smooth scrolling.
Table of Contents
Automatic table of contents generation from page headings with active section highlighting.

The On this page panel on the right is the table of contents, built from the page’s h2–h6 headings.
Overview
- Auto-Generated: Extracts from h2-h6 headings
- Scroll Spy: Highlights current section
- Smooth Scroll: Animated navigation
- Responsive: Sidebar on desktop, offcanvas on mobile
Implementation
Include Template
{% include content/toc.html %}
TOC Generation
The toc.html include uses Kramdown’s built-in TOC:
<nav id="TableOfContents" class="toc">
<h2 class="toc-title">On This Page</h2>
{{ content | toc_only }}
</nav>
Or manual extraction:
<nav id="TableOfContents">
<ul class="toc-list">
{% for heading in page.content | split: '<h' %}
{% if heading contains 'id="' %}
{% assign id = heading | split: 'id="' | last | split: '"' | first %}
{% assign level = heading | slice: 0, 1 %}
{% assign text = heading | split: '>' | last | split: '<' | first %}
<li class="toc-item toc-level-{{ level }}">
<a href="#{{ id }}" class="toc-link">{{ text }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>
Configuration
Enable TOC
In front matter:
---
toc: true
---
Or site-wide in _config.yml:
defaults:
- scope:
type: docs
values:
toc: true
Heading Levels
Configure which headings appear:
toc:
min_level: 2 # Start at h2
max_level: 4 # End at h4
Styling
Basic Styles
.toc {
position: sticky;
top: 80px;
max-height: calc(100vh - 100px);
overflow-y: auto;
}
.toc-title {
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 1rem;
}
.toc-list {
list-style: none;
padding: 0;
margin: 0;
}
.toc-link {
display: block;
padding: 0.25rem 0;
color: var(--bs-secondary);
text-decoration: none;
font-size: 0.875rem;
border-left: 2px solid transparent;
padding-left: 0.75rem;
}
.toc-link:hover {
color: var(--bs-primary);
}
.toc-link.active {
color: var(--bs-primary);
border-left-color: var(--bs-primary);
font-weight: 500;
}
Nested Levels
.toc-level-3 {
padding-left: 1rem;
}
.toc-level-4 {
padding-left: 2rem;
font-size: 0.8125rem;
}
Scroll Spy
Intersection Observer
function initScrollSpy() {
const headings = document.querySelectorAll('h2[id], h3[id], h4[id]');
const tocLinks = document.querySelectorAll('.toc-link');
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
tocLinks.forEach((link) => link.classList.remove('active'));
const activeLink = document.querySelector(
`.toc-link[href="#${entry.target.id}"]`
);
activeLink?.classList.add('active');
}
});
},
{ rootMargin: '-20% 0% -70% 0%' }
);
headings.forEach((heading) => observer.observe(heading));
}
Smooth Scrolling
CSS Method
html {
scroll-behavior: smooth;
}
JavaScript Method
document.querySelectorAll('.toc-link').forEach((link) => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href').slice(1);
const target = document.getElementById(targetId);
const headerOffset = 80;
const position = target.offsetTop - headerOffset;
window.scrollTo({
top: position,
behavior: 'smooth'
});
history.pushState(null, '', `#${targetId}`);
});
});
Responsive Behavior
Desktop
TOC appears in right sidebar:
<aside class="d-none d-lg-block">
{% include content/toc.html %}
</aside>
Mobile
TOC in offcanvas (see Mobile TOC):
<div class="offcanvas offcanvas-end d-lg-none" id="tocSidebar">
{% include content/toc.html %}
</div>
Accessibility
ARIA Attributes
<nav id="TableOfContents"
aria-label="Table of contents"
role="navigation">
Keyboard Navigation
- Tab through TOC links
- Enter to navigate to section
- Focus moves to heading
Troubleshooting
TOC Not Generating
- Verify headings have IDs
- Check
toc: truein front matter - Ensure Kramdown processor
Scroll Spy Not Working
- Check heading IDs match TOC hrefs
- Verify Intersection Observer support
- Test observer margins
Styling Issues
- Check sticky positioning
- Verify z-index
- Test overflow behavior
Related
See also
- [[Features]]
- [[Mobile TOC Floating Action Button]]
- [[Sidebar Navigation System]]