Table of Contents
Introduction
Hugo, the fast static site generator, makes it easy to generate a table of contents (TOC) for your articles by using the .TableOfContents property. However, customizing TOC display, hiding it when it’s empty, styling it, and supporting multilingual titles can be tricky. This article covers best practices and reliable patterns—both basic and advanced—for displaying and controlling your TOC in Hugo.
1. The Basic Approach
Hugo automatically generates a TOC from your headings (typically h2–h4) via:
{{ .TableOfContents }}
Add this line to your template where you want the TOC to appear. But this approach leaves little room for styling or title adjustments.
2. Styling and Adding a Title
If you want to wrap the TOC in a styled box and add a heading:
<div class="toc-wrapper" style="text-align:center;"> <div class="toc-inner" style="display:inline-block; background:#f8f8f8; border:1px solid #ccc; border-radius:10px; padding:1em 2em;"> <h2> Table of Contents </h2> <nav id="TableOfContents" style="text-align:left;"> {{ .TableOfContents }} </nav> </div> </div>
This approach lets you easily center the TOC, add borders, backgrounds, padding, and more.
3. Display the Title in Multiple Languages (without i18n files)
For a simple multilingual switch (without Hugo’s i18n files), use a conditional:
<h2>
{{ if eq $.Site.Language.Lang "ja" }}
目次
{{ else }}
Table of Contents
{{ end }}
</h2>
Where $.Site.Language.Lang will be “ja” for Japanese and, for all else, defaults to English.
4. The Big Problem: Hiding the TOC When There Are No Headings
The real pain point: Even if there are no headings, .TableOfContents can still output an empty HTML element or empty string. So {{ if .TableOfContents }} or {{ with .TableOfContents }} often fail to suppress the TOC box and its title.
Robust Solution: Strip tags and check the text length!
{{ $toc := .TableOfContents }}
{{ if gt (len (plainify $toc)) 0 }}
<div class="toc-wrapper toc-box" style="text-align:center; margin:2em auto;">
<div class="toc-inner" style="display:inline-block; background:#f8f8f8; border:1px solid #ccc; border-radius:10px; padding:1em 2em;">
<h2>
{{ if eq $.Site.Language.Lang "ja" }}目次{{ else }}Table of Contents{{ end }}
</h2>
<nav id="TableOfContents" style="text-align:left;">
{{ $toc | safeHTML }}
</nav>
</div>
</div>
{{ end }}
- plainify strips HTML tags, leaving only raw text.
- len checks if there is actually any content to display—if not, nothing is rendered, including the heading/title.
5. Avoiding Template Errors (Scope Issues)
If you encounter errors like can’t evaluate field Site in type template.HTML, it’s because in a block like {{ with .TableOfContents }}, the dot (.) changes scope! You must use $.Site.Language.Lang to access the page’s site context in that scope.
Alternatively, set a variable at the top:
{{ $page := . }}
...
{{ if eq $page.Site.Language.Lang "ja" }}Japanese Title{{ else }}English Title{{ end }}
6. Sample CSS for a Nice TOC Box
text
.toc-wrapper {
text-align: center;
margin: 2em auto;
}
.toc-box .toc-inner {
display: inline-block;
background: #f8f8f8;
border: 1px solid #ccc;
border-radius: 10px;
padding: 1em 2em;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
#TableOfContents {
text-align: left;
}
Conclusion
- Use .TableOfContents for easy automatic TOC generation.
- Style the TOC box and heading as you like.
- Use language-aware conditionals to show the TOC title in your readers’ language.
- Use the plainify/len technique for the most robust “hide when empty” logic—it’s 100% reliable!
- Remember: with Hugo templates, always pay close attention to context ($ for the root/page context when needed).
If you want a TOC that feels professional and never shows up empty, use the snippets above as your best practice!
Leave a Reply