
How to add a table of contents
Let your readers know what to expect in your posts and give them quick links to navigate content quickly by adding a table of contents with the Tocbot library.
Having a table of contents on your site is a nice creature comfort for post readers – having one that's automatically generated and always up to date is nice for post authors. In this tutorial, we’ll show you how to add an automatically generated table of contents to your Ghost site in a few quick steps.
Here's an example of what we'll build:
Tocbot
To help us add the table of contents, we’re going to use a JavaScript (JS) library called Tocbot. This library does the heavy lifting for us, which includes finding out the post’s headings, creating links to those sections, and showing the reader where they are on the page. It’s a super cool library!
<h2>
. Create a heading in Ghost by selecting your heading text and clicking the H
icon in the popup editor.There are two parts to the Tocbot library: 1) a JS file that handles functionality and 2) a CSS file that handles basic styling. Both need to be loaded into your Ghost theme.
In this tutorial, we’ll be using Ghost’s default theme, Casper. While the steps apply to any Ghost theme, you'll need to modify the code to work with your particular theme.
Edit default.hbs
Open default.hbs
in a code editor. In this file, add the Tocbot script and styles as explained below.
Add the CSS
Near the top of the default.hbs
file, in the head
tag and right before {{ghost_head}}
, add the Tocbot CSS:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.3/tocbot.css">
Additional styles
We'll add some additional styles to a style
tag directly after the CSS we just loaded in default.hbs
.
These styles will help Tocbot look right at home in our theme. While what's shared below is specific to Casper, you can adapt the style to work with whatever theme you’re using.
Our goal for the TOC is for it to be present alongside article content on larger screens but above it on smaller ones.
<style>
.gh-content {
position: relative;
}
.gh-toc > .toc-list {
position: relative;
}
.toc-list {
overflow: hidden;
list-style: none;
}
@media (min-width: 1300px) {
.gh-sidebar {
position: absolute;
top: 0;
bottom: 0;
margin-top: 4vmin;
grid-column: wide-start / main-start; /* Place the TOC to the left of the content */
}
.gh-toc {
position: sticky; /* On larger screens, TOC will stay in the same spot on the page */
top: 4vmin;
}
}
.gh-toc .is-active-link::before {
background-color: var(--ghost-accent-color); /* Defines TOC accent color based on Accent color set in Ghost Admin */
}
</style>
Add the JS
In default.hbs
, now near the end of the file and right before {{ghost_foot}}
, add the Tocbot script:
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.3/tocbot.min.js"></script>
Initialize the Tocbot script
Having loaded the Tocbot script, we now need to initialize it, which tells Tocbot where on the page we want our table of contents and what we want to add to it.
Add this script right after the code from the last step:
{{! Initialize Tocbot after you load the script }}
<script>
tocbot.init({
// Where to render the table of contents.
tocSelector: '.gh-toc',
// Where to grab the headings to build the table of contents.
contentSelector: '.gh-content',
// Which headings to grab inside of the contentSelector element.
headingSelector: 'h1, h2, h3, h4',
// Ensure correct positioning
hasInnerContainers: true,
});
</script>
gh-content
, gh-toc
) used in this tutorial are based on the Casper theme. You’ll need to change gh-content
to match the container class (what wraps around your post content) of your theme. gh-toc
can be anything you want – you just need to ensure that the class in the initialization script matches the one in the template (as shown in the next step).Edit post.hbs
Let's define where we want the table of contents to show up in our theme.
In post.hbs
, add the following code snippet right before the {{content}}
helper:
<aside class="gh-sidebar"><div class="gh-toc"></div></aside>
🔥 Fire up your table of contents
You've made some sweet progress: Tocbot is installed and you’ve hooked it up to your Ghost theme.
As a final check, your default.hbs
and post.hbs
files should look like this:
<!DOCTYPE html>
<html lang="" class="dark-mode" class="auto-color">
<head>
<title></title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.3/tocbot.css">
<style>
.gh-content {
position: relative;
}
.gh-toc > .toc-list {
position: relative;
}
.toc-list {
overflow: hidden;
list-style: none;
}
@media (min-width: 1300px) {
.gh-sidebar {
position: absolute;
top: 0;
bottom: 0;
margin-top: 4vmin;
grid-column: wide-start / main-start; /* Place the TOC to the left of the content */
}
.gh-toc {
position: sticky; /* On larger screens, TOC will stay in the same spot on the page */
top: 4vmin;
}
}
.gh-toc .is-active-link::before {
background-color: var(--ghost-accent-color); /* Defines TOC accent color based on Accent color set in Ghost Admin */
}
</style>
</head>
<body class=" has-serif-title has-sans-body has-cover no-logo">
<div class="viewport">
<header id="gh-head" class="gh-head outer">
<nav class="gh-head-inner inner">
<div class="gh-head-brand">
<a class="gh-head-logo no-image" href="">
<img src="" alt="" />
</a>
<a class="gh-burger" role="button">
<div class="gh-burger-box">
<div class="gh-burger-inner"></div>
</div>
</a>
</div>
<div class="gh-head-menu">
</div>
<div class="gh-head-actions">
<div class="gh-social">
<a class="gh-social-link gh-social-facebook" href="" title="Facebook" target="_blank" rel="noopener"></a>
<a class="gh-social-link gh-social-twitter" href="" title="Twitter" target="_blank" rel="noopener"></a>
</div>
<a class="gh-head-button" href="#/portal/signup" data-portal="signup">Subscribe</a>
<a class="gh-head-button" href="#/portal/account" data-portal="account">Account</a>
</div>
</nav>
</header>
<div class="site-content">
</div>
<footer class="site-footer outer">
<div class="inner">
<section class="copyright"><a href=""></a> © </section>
<nav class="site-footer-nav">
</nav>
<div><a href="https://ghost.org/" target="_blank" rel="noopener">Powered by Ghost</a></div>
</div>
</footer>
</div>
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous">
</script>
<script src=""></script>
<script>
$(document).ready(function () {
// Mobile Menu Trigger
$('.gh-burger').click(function () {
$('body').toggleClass('gh-head-open');
});
// FitVids - Makes video embeds responsive
$(".gh-content").fitVids();
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.12.3/tocbot.min.js"></script>
<script>
tocbot.init({
// Where to render the table of contents.
tocSelector: '.gh-toc',
// Where to grab the headings to build the table of contents.
contentSelector: '.gh-content',
// Which headings to grab inside of the contentSelector element.
headingSelector: 'h1, h2, h3, h4',
// Ensure correct positioning
hasInnerContainers: true,
});
</script>
</body>
</html>
<main id="site-main" class="site-main">
<article class="article image-full image-small">
<header class="article-header gh-canvas">
<div class="article-tag post-card-tags">
<span class="post-card-primary-tag">
<a href=""></a>
</span>
<span class="post-card-featured"> Featured</span>
</div>
<h1 class="article-title"></h1>
<p class="article-excerpt"></p>
<div class="article-byline">
<section class="article-byline-content">
<ul class="author-list">
<li class="author-list-item">
<a href="" class="author-avatar">
<img class="author-profile-image" src="" alt="" />
</a>
<a href="" class="author-avatar author-profile-image"></a>
</li>
</ul>
<div class="article-byline-meta">
<h4 class="author-name"></h4>
<div class="byline-meta-content">
<time class="byline-meta-date" datetime=""></time>
<span class="byline-reading-time"><span class="bull">•</span> </span>
</div>
</div>
</section>
</div>
<figure class="article-image">
<img
srcset=" 300w,
600w,
1000w,
2000w"
sizes="(min-width: 1400px) 1400px, 92vw"
src=""
alt=""
/>
<figcaption></figcaption>
</figure>
</header>
<section class="gh-content gh-canvas">
<aside class="gh-sidebar"><div class="gh-toc"></div></aside>
</section>
</article>
</main>
<section class="footer-cta outer">
<div class="inner">
<h2 class="footer-cta-title"></h2>
<a class="footer-cta-button" href="#/portal" data-portal>
<div class="footer-cta-input">Enter your email</div>
<span>Subscribe</span>
</a>
</div>
</section>
" limit="3" as |more_posts|}}
<aside class="read-more-wrap outer">
<div class="read-more inner">
</div>
</aside>
You’re now ready to upload your changes to your Ghost site. Activate your new theme, refresh a post, and watch your own little table-of-contents robot work its magic 🤖

Summary
In this tutorial, you installed a third-party JS library, modified a Ghost theme template, and added custom CSS. You’re well on your way to becoming a Ghost pro. Keep on leveling up your Ghost skills by checking out more of our tutorials or head on over to our Forum to share how you built your table of contents.