17.3 Table of Contents: .TableOfContents
Right, so you want a table of contents. Seems simple enough. You slap a .TableOfContents on your page and Hugo, being the reasonably intelligent software it is, goes and builds one for you. And it does! Mostly. But like any good friendship, ours with Hugo requires understanding its quirks. It’s not psychic, and it makes a few… let’s call them “interesting assumptions” about your content structure.
The most basic incantation is exactly what you’d expect. You drop this shortcode into a template—usually a _default/single.html or a dedicated section template—and magic happens.
{{ .TableOfContents }}
But what you get is a neatly nested <nav> element with the id="TableOfContents", filled with <ul>s and <li>s linking to your headings. It’s a perfectly serviceable start. However, the devil, as always, is in the details. Or more specifically, in your Markdown headers.
How Hugo Builds the Damn Thing
Hugo doesn’t just read your page and guess. It formally parses your content’s Markdown, plucks out the heading elements (the ## ones), and constructs the TOC from that hierarchy. This is crucial: if it’s not in a heading, it won’t be in the TOC. This seems obvious, but you’d be surprised how many people forget they used bold text instead of an actual ### header and then wonder why their TOC is empty.
The hierarchy is everything. A ## (H2) becomes a top-level item. A ### (H3) becomes a nested list item under the last H2 it appeared after. This is why structuring your document logically isn’t just good practice; it’s a prerequisite for a TOC that doesn’t look like a bowl of spaghetti.
Controlling the Scope with Headers Levels
By default, Hugo’s TOC includes headings from H2 (##) through H6. But maybe your page has an H1 title and you only want H2 and H3 in the TOC. Hugo gives you the knobs to adjust this in your config.
[markup]
[markup.tableOfContents]
startLevel = 2
endLevel = 3
This tells Hugo, “Hey, buddy, only include headings from level 2 to level 3 in that TOC.” This is incredibly useful for keeping your TOC focused and not overwhelming the reader with a million nested items from super-deep headings.
The Silent Failure: When Your TOC is Empty
You’ve added {{ .TableOfContents }}, you’ve refreshed the page, and… nothing. No <nav>, no list, just a blank spot on the page. This is the most common “problem.” Nine times out of ten, it’s for one of two reasons:
- Your content has no headings. Go check. I’ll wait.
- You’ve set
startLevelandendLevelin your config that exclude all the headings you actually used. If you have only H4s (####) in your content but your TOC is configured to only show H2s and H3s, it will render precisely squat.
Styling Your Way Out of Ugly-Town
The default output is functional but, let’s be honest, it’s often ugly. It’s a nested list, and it will inherit the basic styles of your theme, which might be a mess. You’ll almost certainly want to style it.
Because Hugo outputs a predictable structure, you can target it with CSS. The #TableOfContents ID is your best friend here.
#TableOfContents {
font-size: 0.9rem;
border-left: 2px solid #eee;
padding-left: 1em;
}
#TableOfContents ul {
list-style: none;
padding-left: 1em;
}
#TableOfContents li {
margin: 0.5em 0;
}
This is a minimal example to make it look less like a default browser list and more like a sleek sidebar nav. The key takeaway: the HTML structure is consistent, so your CSS can be precise and effective.
The .TableOfContents vs .Page.TableOfContents Smackdown
Here’s a bit of arcane knowledge that might save you an hour of frustration. In your templates, you might see both .TableOfContents and .Page.TableOfContents. What’s the difference?
.TableOfContentsis a method on the current page context. It works perfectly in, say, your single.html template..Page.TableOfContentsis used when you’re in a different context, like ranging over a list of pages. For example, if you’re on the home page and you want to show a TOC for each page in a list, you’d need to call.Page.TableOfContentsbecause the context for each item in the loop is the page itself, not the TOC method of the home page.
In 99% of cases within a single page template, you just want the plain, simple .TableOfContents.
So there you have it. It’s a powerful, simple tool, but like any tool, it works best when you know why it behaves the way it does. Structure your headings well, configure its scope, and style the results. Do that, and you’ll have a TOC that actually helps instead of just taking up space.