Add an Automatic Table of Contents in Hugo

A table of contents helps with a long technical article and gets in the way of a short one. Hugo already knows the Markdown heading structure, so the implementation is one template value rather than a JavaScript library.

Hugo has already built the outline

In a single-page template, output:

{{ .TableOfContents }}

Given headings such as:

## First section

### Smaller section

## Second section

Hugo produces a nested list of links to the generated heading IDs. For example:

## Browser support

may render as:

<h2 id="browser-support">
  Browser support
</h2>

The table of contents points to that ID and stays in sync when the headings change.

Make it an article-level choice

Not every post needs an outline occupying the top of the page. I prefer an explicit front matter value:

toc: true

Then the template can add a label and navigation landmark only where requested:

{{ if .Params.toc }}
  <aside class="toc">
    <h2>Contents</h2>
    {{ .TableOfContents }}
  </aside>
{{ end }}

This keeps short notes short without inventing a fragile heading-count check in the template.

Include useful heading levels

The page title is normally the H1, while article sections begin at H2. A practical Hugo configuration includes H2 and H3:

[markup.tableOfContents]
  startLevel = 2
  endLevel = 3
  ordered = false

Going much deeper can turn the outline into a second article. If a table of contents needs five nested levels, I would first check whether the article structure is doing too much.

Keep the presentation modest

Hugo normally emits markup containing:

<nav id="TableOfContents">
  <ul>
    ...
  </ul>
</nav>

The wrapper from the template gives it a styling hook:

.toc {
  padding: 1rem;
  border: 1px solid #ddd;
}

.toc ul {
  padding-left: 1.25rem;
}

.toc a {
  text-decoration: none;
}

On wide layouts, a sticky sidebar can work well:

.toc {
  position: sticky;
  top: 2rem;
}

On a phone, that same sidebar can consume most of the useful screen. A normal block near the article opening is often enough, or it can be collapsed with native HTML:

<details class="toc">
  <summary>Contents</summary>
  ...
</details>

Heading render hooks can also add direct # links beside headings, which is useful when readers share a particular section.

I enable a table of contents for guides that are long enough to need navigation, not because every article template has an empty space for one. Hugo makes it cheap to generate; editorial judgment still decides where it helps.

comments