Table of Contents in Ghost

Automatically generated table of contents in Ghost, conditionally shown when it is not empty.

Table of Contents in Ghost

Update 3/2023, also check this out, it's simpler

How to add a table of contents in Ghost without editing the site template
How to add a table of contents in Ghost without editing the site template

Auto Generate TOC

I largely followed the excellent instructions here

Ghost Docs
Everything you need to know about working with the Ghost professional publishing platform.

Adding TOC: see commit

Styling TOC: see commit

Note: there is a recent fix in the theme regarding scroll-margin-top

.post-full-content h1,
.post-full-content h2,
.post-full-content h3,
.post-full-content h4,
.post-full-content h5,
.post-full-content h6 {
    color: color-mod(var(--darkgrey) l(-5%));
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
    scroll-margin-top: 110px;


It makes the TOC scrolling a bit weird and is redundant with, we can keep either one of them.

.post-content h2::before,
.post-content h3::before {
    display: block;
    content: " ";
    height: 84px;
    margin-top: -84px;
    visibility: hidden;


Make it Conditional 🎉

Now, there are a few problems. Because the styling is applied on all .post-full-content there are two issues

  1. Post without TOC will still have its main text area off-centered to make room for the TOC that doesn't exist
  2. Pages will not have POC (because we only adjusted post.hbs, but it will also make its content off-centered for TOC area on the right

The main thing making this extra column appear is

.post-full-content {
    display: grid;
    grid-template-columns: 1fr 0.2fr;
    padding: 0 0 6vw;
    margin: 0;

We can give it a more special name .post-full-content-with-toc.

Then we only add this class to post pages which actually have a non empty TOC. One way to achieve this is to add the following Javascript somewhere, for example in post.hbs's script block (see commit here).

var hasTOC = $(".toc").children().length
if (hasTOC) 

Optional Configurations

Optionally, you can also disable TOC when there aren't say more than 5 entries in the TOC list.

var hasTOC = $(".toc").children().length;
if (hasTOC) {
    var n = $(".toc li").length;
    if (n < 5) {
    } else {

One small thing related to membership, when a reader does not have access to a post, there will be a call to action (CTA) but no content, in this case it doesn't make sense to have a TOC either. To adjust for this, wrap the previous <aside> section with {{#if access}} to check whether the user has access to the post.

{{#if access}}
<aside class="toc-container">
    <div class="toc"></div>

See here for more Ghost editing tips: 👇
Using Ghost - Ran Ding
Notes on using Ghost platform.