Feature image

ghost-toc-plugin preview

Change options in the left panel. The table of contents re-renders live and the embed code updates with it. Scroll to see the current section highlighted.

Getting started

ghost-toc-plugin is a tiny (~3 KB), dependency-free floating table of contents for Ghost. It started as a Ghost widget, but it also works on Notion-based blogs, and anywhere you can add a script. It reads the headings in your post, builds the list automatically, follows you as you scroll, and highlights the section you are reading. This page is a live example: the list on the side is the real widget.

Installation

Add it once with this snippet, no build step, no theme files to edit:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/GreedyLabs/ghost-toc-plugin@1/toc.css">
<script src="https://cdn.jsdelivr.net/gh/GreedyLabs/ghost-toc-plugin@1/toc.min.js"
        data-content=".gh-content"
        data-headings="h2,h3"
        data-title="Contents"></script>

Code injection

In Ghost, paste it into Settings → Code injection → Site Footer. On Notion-based hosting, add it in the custom-code field: oopy has a head/footer code box in its site settings, super.so has a Custom Code area, and there you set data-content to .notion-page-content. On any other site, put it just before the closing </body> tag. Keep it without async or defer so it can read its own data-* options.

Options

Everything is configured with data-* attributes on the script tag: the content selector, which headings to include, the title, the position, and more:

data-content=".gh-content"
data-headings="h2,h3"
data-position="right"
data-min-width="1200"
data-top="100"

Customize

Colors and placement are fully adjustable. Every class and CSS variable is namespaced under greedylabs-ghost-toc, so the widget never clashes with your theme.

Colors

The active item follows your Ghost theme accent (--ghost-accent-color) by default; set data-accent or override the CSS variables to change it. The widget ships light and dark defaults and adapts to the system theme automatically. To boost readability in dark mode, override the colors for dark only, a single fixed value would look off in light:

@media (prefers-color-scheme: dark) {
  .greedylabs-ghost-toc {
    --greedylabs-ghost-toc-muted: #c9d1d9;
    --greedylabs-ghost-toc-border: rgba(255,255,255,.22);
  }
}

Position

Use data-position for left or right, data-top for the offset from the top, data-gap for the space from your content, and data-width for the panel width. Below data-min-width (or when there is no room beside the content) the table of contents hides itself.

FAQ

Does the widget slow my site down?

Barely. It's about 3KB with no dependencies and no external requests. Scroll is handled with a passive listener and coalesced into one calculation per frame via requestAnimationFrame; it only recomputes on document-height changes (lazy images, giscus and similar widgets) through a ResizeObserver.

Why isn't the table of contents showing?

Check three things: that your data-content selector matches the real article container, that there are at least two headings (it will not render below that), and that the viewport is wider than data-min-width (1200px by default) with room for the panel beside the content. On narrow screens and mobile it hides on purpose.

Can I include deeper headings like h4 in the TOC?

Yes. List the tags you want in data-headings, comma-separated (e.g. h2,h3,h4). You choose exactly which heading levels appear in the list.

Wrapping up

ghost-toc-plugin is open source under the MIT license; the code and issues live on GitHub. Try the options in the left panel, watch the preview update live, and copy the embed snippet when you are happy. If it helped you, a coffee is always appreciated ☕.