On this page:

When I’m creating blog posts that describe a process I like to use headers to seperate the main steps. I use <h2> for my groups and <h3> for sub-topics. So when I recently began using Bryan Braun’s AnchorJS to automatically create linkable anchors, it did not take long for me to want to also add a table of contents based on those anchors.

Using AnchorJS

After looking at a few options I landed on AnchorJS because I wanted something simple that “just worked” and did not require any Jekyll plugins.

I wanted to add the AnchorJS reference right at the end of the body section so I opened _layouts/default.html and added the following before </body>

<script src="https://cdn.jsdelivr.net/npm/anchor-js/anchor.min.js"></script>

Next I added a file named js/anchorjs-toc.js and I added the following content to is:

anchors.options.visible = 'always';
anchors.add('h2, h3');

And in _layouts/default.html I added the following immediately after the anchor.min.js included a moment ago.

<script src="/js/anchorjs-toc.js"></script>

I choose these options because I like the look of always-on link icons and I only wanted these on the h2 and h3 headers. The post/page title in the hero area is the h1 header and h4+ are too granular.

Optional: Disabling Dynamic Content Rendering

Sometimes the header links would render and sometimes they wouldn’t. I set a breakpoint and found that sometimes the content was rendered already (allowing anchorjs to find the headers) and sometimes it wasn’t (so it couldn’t). Assuming it was some lazy rendering thing, I found the _data/settings.yml ajax_loading setting was true so I changed it to false and now it was reliably rendering anchor links correctly.

This is likely only relevant to Jekyll and possibly only to the theme I’m using. Just keep it in mind, though, if your links are not rendering consistently.

Creating the Table of Contents

Using JavaScript to Generate the Content

Now that AnchorJS is installed and working correctly, I followed the jsfiddle template that Bryan created as an example. I copied the functions generatedTableofContents and addNavItem into the js/anchor-toc.js file I created in the previous steps (I did change them slightly - see the note below the code).

I also added a call to generateTableOfContents.

The contents of the file are:

anchors.options.visible = 'always';
anchors.add('h2, h3');
generateTableOfContents(anchors.elements);

// External code for generating a simple dynamic Table of Contents
function generateTableOfContents(els) {
  var anchoredElText,
      anchoredElHref,
      ul = document.createElement('UL');

  document.getElementById('table-of-contents').appendChild(ul);

  for (var i = 0; i < els.length; i++) {
    anchoredElText = els[i].textContent;
    anchoredElHref = els[i].querySelector('.anchorjs-link').getAttribute('href');
    addNavItem(ul, anchoredElHref, anchoredElText, 'toc-li-'.concat(els[i].tagName));
  }
}

function addNavItem(ul, href, text, listClass) {
  var listItem = document.createElement('LI'),
      anchorItem = document.createElement('A'),
      textNode = document.createTextNode(text);
      listItem.className = listClass;
  
  anchorItem.href = href;
  ul.appendChild(listItem);
  listItem.appendChild(anchorItem);
  anchorItem.appendChild(textNode);
}

Please note that I have made two changes to this file.

  1. I changed line 16 to pass 'toc-li-'.concat(els[i].tagName) as a fourth parameter to addNavItem. This string will be toc-li-h2 or toc-li-h3 depending on whether we are at an h2 or h3 tag.
  2. I added line 24 listItem.className = listClass; to addNavItem

These two changes ensure that the list items for the table of content include different classes for h2 and h3 tags. I’ll be using this information to format them differently.

Creating the Table of Contents Include

Knowing I would want a repeatable ToC, I next added _includes/toc.html which contains the following:

<noscript>
  <style type="text/css">
    #table-of-contents { display:none; }
  </style>
</noscript>

<div id="table-of-contents">
  <div id="toc-header">On this page:</div>
</div>

The noscript section hides the ToC div when javascript is disabled - I try to have a reasonable no-js experience.

And this can be included on any page like so:


{% include toc.html %}

Now when page is rendered, the table of contents display - but they are kind of ugly.

Formatting the Table of Contents

To format the table of contents I started with the example here and created the file _scss/_includes/_toc.sccs which included the following:

#table-of-contents {
    float: right;
    background: #f9f9f9 none repeat scroll 0 0;
    border: 1px solid #aaa;
    display: table;
    margin: 20px 1em 1em 1em;
    padding: 10px;
    width: auto;
    max-width: 35%;
}

#toc-header {
    font-weight: 900;
    font-size: 125%;
}

#table-of-contents li, #table-of-contents ul, #table-of-contents ul li{
    list-style: outside none none !important;
}

li.toc-li-H2 {
    margin-left: 1em;    
}

li.toc-li-H3 {
    margin-left: 2em;
    font-size: 85%;
    filter: brightness(2.50);
}

Basically, this creates a box in which the table of contents will exist. It floats that box to the right and applies some margin and padding. Next, I apply some formatting to the “On this page” header. Finally, I use the li’s class (toc-li-h2 or toc-li-h3) to conditionally format the entries to approximate the look of a hierarchy. The h2’s are slightly larger than the h3’s, the h3’s are slightly more indented, and the h3’s are slightly lighter in color.

I also needed to make sure this file was included when the site was built so I added:

@import "_includes/toc"

To css/style.scss.

And this pulled it all together into a table of contents that renders the way I preferred.

Screenshot of the table of contents
Screenshot of the table of contents