Bengal automatically generates a table of contents from page headings. Access it viapage.toc.
The Pattern
{% if page.toc %}
<nav class="toc" aria-label="On this page">
<h2>On this page</h2>
{{ page.toc | safe }}
</nav>
{% end %}
That's it. Bengal parses headings and generates nested HTML lists.
What's Happening
| Component | Purpose |
|---|---|
page.toc |
Pre-rendered HTML list of headings |
| safe |
Render as HTML, not escaped text |
Control Which Headings
Configure TOC depth inbengal.toml:
[content]
toc_depth = 4 # Maximum heading depth (1-6). Default: 4
This controls how deep the TOC goes. For example:
toc_depth = 2includes only H2 headingstoc_depth = 3includes H2 and H3 headingstoc_depth = 4includes H2, H3, and H4 headings (default)
For parser-specific control, usemarkdown.toc_depthwith a range string:
[markdown]
toc_depth = "2-4" # String format for parser-level control
Variations
---
title: Simple Page
toc: false
---
Then in template:
{% if page.toc and page.metadata.toc != false %}
{{ page.toc | safe }}
{% end %}
<div class="article-layout">
<aside class="sidebar">
{% if page.toc %}
<nav class="toc">
<h2>On this page</h2>
{{ page.toc | safe }}
</nav>
{% end %}
</aside>
<main>
<h1>{{ page.title }}</h1>
{{ page.rendered_html | safe }}
</main>
</div>
{# Only show TOC for docs section #}
{% if page.section == 'docs' and page.toc %}
<nav class="toc">
{{ page.toc | safe }}
</nav>
{% end %}
Scroll Highlighting (JavaScript)
Highlight current section as user scrolls — this part is standard JS, not Bengal-specific:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
document.querySelectorAll('.toc a').forEach(a => a.classList.remove('active'));
document.querySelector(`.toc a[href="#${entry.target.id}"]`)?.classList.add('active');
}
});
}, { rootMargin: '-20% 0px -80% 0px' });
document.querySelectorAll('h2[id], h3[id]').forEach(h => observer.observe(h));
Seealso
- Template Variables — All page properties
- Configuration — Markup options