Adding a floating TOC to the Hugo-Clarity theme

Hugo posts with a Table of Contents

For some of my lengthier posts, a table of contents (TOC) would help with reading the post, and especially when going over certain parts of the posts.

A good example is the original 'Wireguard VPN access' post I made (https://www.nodinrogers.com/post/2022-01-01-wireguard ), which had a lot of steps I detailed, to break it down step by step, to make it easier to understand and implement.

In the example below, I'm going to use the exampleSite that comes with the Hugo-Clarity theme, and add a TOC to it.

Without a TOC, or any modifications, the exampleSite looks like this:

Hugo-Clarity exampleSite Markdown Syntax Guide page

Out of the box, Hugo has the ability to add a TOC by adding this shortcode to your page:

{{ .TableOfContents }}

The Hugo theme Hugo-Clarity uses this shortcode, which results in the TOC being at the top of the page, and quickly scrolls off the page.

At the per-post level, simply adding toc: true to the Front Matter section of the post will add a TOC.

The TOC, as shown in the Hugo-Clarity exampleSite

Floating TOC makes reading easier

I'd seen a couple of sites that had a floating TOC on the side, which helped navigate as the TOC never left the screen, and points to the specific section of the page that you're currently viewing.

While Hugo has the TOC shortcode built in, there are some changes that need to made to the Hugo-Clarity theme in order to get the TOC to float.

Creating local versions of the theme files

The key to customizing Hugo theme files, and the Hugo-Clarity theme is no different, is to make a copy of any file you want to customize, and customize that file.

In this case, we want to customize/modify the single.html and _components.sass files to implment the floating TOC.

Hugo-Clarity, and any other Hugo theme, uses the files in themes directory, unless a copy exists locally.

Example, for the file single.html:

The file themes/hugo-clarity/layouts/_default/single.html is used unless a local copy exists in layouts/_default/single.html, in which case that file is used, and the original ignored.

All commands assume you are in the root directory of your Hugo site.

To implement the floating TOC, we need to customize 2 files:

layout/_default/single.html
assets/sass/_components.sass

To copy the above files to local directories, so we can customize them:

mkdir layouts/_default assets/sass
cp themes/hugo-clarity/layouts/_default/single.html layouts/_default/
cp themes/hugo-clarity/assets/sass/_components.sass assets/sass/

From a file structure perspective, the 2 copies of single.html file looks like this:

tree layouts/_default/ themes/hugo-clarity/layouts/_default/
layouts/_default/
`-- single.html
themes/hugo-clarity/layouts/_default/
|-- _markup
|   `-- render-image.html
|-- baseof.html
|-- index.json
|-- list.html
|-- rss.xml
`-- single.html

We will be customizing layouts/_default/single.html and assets/sass/_components.sass.

Modify the single.html file

The default single.html file loads the TOC at the top of the page, but we want it to show up in the sidebar area, so the first thing we need to do is remove the TOC code section from the main page.

The customization below will display only the TOC and not the sidebar, if toc: true is in the Front Matter of the post.

I use this logic as only a few of my pages will have a TOC.

If you use a TOC on most/all of your pages, you may want to change the logic, and perhaps have the TOC over the sidebar content, at least until the TOC scrolls off, assuming your page is long enough in content.

Delete these lines from layouts/_default/single.html file:

{{ if $p.toc }}
  <div class="post_toc">
    <h2>{{ T "overview" }}</h2>
    {{ .TableOfContents }}
  </div>
{{ end }}

To load the TOC in the sidebar area, add the below lines after the </article> in the single.html file::

{{ if $p.toc }}
  <p> test</p>
  <aside class="tableOfContentContainer" id="tableOfContentContainer">
    <h3>Table of Contents</h3>
      {{ .TableOfContents }}
 </aside>
{{ else }}
  {{- if ( ne $p.sidebar false ) }}
    {{- partial "sidebar" . }}
  {{ end }}
{{ end }}
</div>

To format the TOC correctly, so the scrolling works correctly to reflect the current section, we need to add some Javascript to the single.html page.

Add this Javascript to the layouts/_default/single.html file, just above the final {{ end }} at the bottom.

<script>
  window.addEventListener('DOMContentLoaded', () => {
    const observerForTableOfContentActiveState = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        const id = entry.target.getAttribute('id');

        if (entry.intersectionRatio > 0) {
          clearActiveStatesInTableOfContents();
          document.querySelector(`nav li a[href="#${id}"]`).parentElement.classList.add('active');
        }
      });
    });
    document.querySelectorAll('h1[id],h2[id],h3[id],h4[id]').forEach((section) => {
      observerForTableOfContentActiveState.observe(section);
    });

  });

  function clearActiveStatesInTableOfContents() {
    document.querySelectorAll('nav li').forEach((section) => {
      section.classList.remove('active');
    });
  }
</script>

Modify the _components.sass file

Now we need to format the TOC so it looks correct and works as we want it to when displayed.

You can customize this to meet your needs/liking, but this will get it functioning properly.

Add these lines to the bottom of the _components.sass file:

aside.tableOfContentContainer
  position: webkit-sticky
  position: sticky
  top: 100px
  align-self: start

nav#TableOfContents li.active::before
  content: "\279c"
  display: inline-block
  width: 20px
  margin-left: -20px

nav#TableOfContents ul
  list-style: none

Final results

Once we have made copies of the single.html file to implement the sidebar TOC, and the _components.sass to properly format the TOC, the end results, IMHO, look much better.

Same page, but with floating TOC

References

Mattias Geniar - Adding a sticky table of contents in Hugo to posts https://ma.ttias.be/adding-a-sticky-table-of-contents-in-hugo-to-posts/

Bramus - Practical Use Cases for Scroll-Linked Animations in CSS with Scroll Timelines https://css-tricks.com/practical-use-cases-for-scroll-linked-animations-in-css-with-scroll-timelines/#scrollspy

Github - chipzoller / hugo-clarity https://github.com/chipzoller/hugo-clarity#getting-up-and-running

Hugo - Front Matter https://gohugo.io/content-management/front-matter/