Neotoc—Table of Contents Reimagined
Welcome! On mobile, tap the bottom-right button to open the TOC. On desktop, you’ll see it right away. Scroll around, fold/unfold, and give it a try if you haven’t already—pretty cool, right?
Let’s get started.
The playground
Check out this CodePen for experimenting with it instantly.
To easily experiment with it in the comfort of your favorite code editor and with some extra tooling, I’ve created a CLI called create-neotoc
. It scaffolds a Vite-powered Neotoc playground. To get started, run the following command in your terminal (you’ll need Node.js installed on your system):
npx create-neotoc
and follow the prompts to set it up. It will look something like below:
Doing npm run dev
will start a local web server. Open the given URL in a web browser and you will find further instructions there.
The playground is just for experimenting and learning. But if you’d like to start from scratch, feel free to go for it!
Installation
From npm:
npm install neotoc@0.2.3
You can also use CDNs like jsDelivr to fetch it. For example:
<script src="https://cdn.jsdelivr.net/npm/neotoc@0.2.3/dist/index.umd.js" defer></script>
It is recommended to use the exact version to avoid any breaking changes.
Adding styles
Neotoc ships some base styles and color schemes to quickly style it.
Base styles
Base styles style everything other than colors. You must need to add a base style, otherwise the TOC will look like broken.
Available base styles:
- Modern: Rounded corners with some subtle decorative styles.
- Plain: No rounded corners and subtle decorative styles.
For example, to load the modern base style from jsDelivr, add the following link tag in the head of your HTML:
<link href="https://cdn.jsdelivr.net/npm/neotoc@0.2.3/dist/base-modern.css" rel="stylesheet">
To use any other base style replace modern
with the lowercase version of that base style name.
Warning
Make sure you load styles from the same version of neotoc that you are using to avoid any mismatch.
If your project setup allows you to import styles in JS/TS files, you can load it like below:
import "neotoc/base-<name-of-the-base-style>.css";
For example:
import "neotoc/base-modern.css";
Color schemes
Available color shemes:
- Zinc: Based on TailwindCSS’s zinc color palette.
- Slate: Based on TailwindCSS’s slate color palette.
- Monochrome: Black and white.
For example, to load the slate color scheme from jsDelivr, add the following link tag in the head of your HTML:
<link href="https://cdn.jsdelivr.net/npm/neotoc@0.2.3/dist/colors-slate.css" rel="stylesheet">
To use any other colorscheme replace slate
with lowercase verion of that colorscheme name.
If your project setup allows you to import styles in JS/TS files, you can load it like below:
import "neotoc/colors-<name-of-the-colorscheme>.css";
For example:
import "neotoc/colors-slate.css";
Requirements
For Neotoc to work properly, it expects two things:
- The headings you target must have unique IDs.
- The headings must appear in a logical order. By logical order, I mean:
- The first heading found determines the highest level. For example, if you start with an
<h2>
heading, you should not use an<h1>
heading later. - There should be no gaps between heading levels when progressing from top to bottom. For instance, after an
<h2>
, you should not use an<h4>
without including an<h3>
in between.
- The first heading found determines the highest level. For example, if you start with an
Neotoc API
You can import the TOC UI generator function directly from the package as shown below:
import neotoc from "neotoc";
You can rename neotoc
to something else if you wish, as it is the default export.
Info
If you load neotoc as an UMD module from a CDN, then the TOC generator function will be available under the neotoc
variable.
neotoc
accepts an options object and returns a cleanup function.
If you’re using Neotoc in React or another library/framework, you’ll likely need to call or return this function where it is required. If you are working with a vanilla HTML, CSS, and JS setup, you typically won’t need to use the cleanup function.
Now, let’s explore all of its options in detail.
Options
io
and to
Type of io
: string
Type of to
: HTMLElement
io
is the only required option. To create a TOC, you must call neotoc
with the io
(input-output) option. Here, you need to answer three questions using CSS selectors separated by >>
. For example:
neotoc({ io: "article >> h2,h3,h4,h5,h6 >> nav" });
- Where should it look for headings?
- Here, it’s the
<article>
element.
- Here, it’s the
- Which headings should it consider for creating the TOC?
- Here, it’s all headings from
<h2>
to<h6>
.
- Here, it’s all headings from
- Where should it append the generated TOC?
- Here, it’s the
<nav>
element.
- Here, it’s the
The last part of the io
value can be omitted if you need to provide the corresponding HTML element directly. You can use the to
option for this purpose.
Note that if the 3rd part of the io
option is specified, to
option is ignored.
title
Type: string
Default value: "On this page"
The title of the TOC UI that appears at the top left of it.
fillAnchor
Type: (heading: HTMLHeadingElement) => string | Node
Default value: (heading) => heading.textContent
It is used to determine the content of anchor elements in the TOC. If your headings have a complex structure or contain elements like <code>
or <i>
, as seen on this page, and you want to display them in the TOC, this option can be useful.
Note
You can’t have the same node in two places in the DOM. You will need to clone it.
onBreadcrumbChange
Type: (data: Breadcrumb) => void
Default value: () => {}
Here Breadcrumb
type is:
type Breadcrumb = {content: string | Node;hash: string;}[]
You can also import the type from Neotoc if needed.
Neotoc provides the data at the right time but leaves the breadcrumb construction in your hands.
The value of content
depends on what is returned from the fillAnchor
option. You will need the hash
if you want to create links for the breadcrumb items to jump to the parent section.
Note: Cleaning up side effects
Since Neotoc leaves breadcrumb construction to you, you will also need to clean up any side effects, if required. This is typically necessary when using it with a frontend library or framework.
ellipsis
Type: boolean
Default value: false
If true
, overflowed text is truncated with … instead of wrapping to the next line. When you hover over the anchor, the full text is displayed in the browser’s default tooltip.
Tip
When using fillAnchor
with cloned heading nodes containing <code>
elements, set their position to static
in the TOC if it’s not, to ensure the ellipsis appears correctly.
classPrefix
Type: string
Default value: nt-
All CSS classes assigned to elements in the TOC UI are prefixed with nt-
by default to avoid class name collisions.
If the nt-
prefix is already used in class names elsewhere on your page, you should set a different value here.
Warning
If you set anything else here, as a side-effect, you can’t directly use styles that come with neotoc. You need to copy these styles and replace all occurrences of the default nt-
prefix with your new class prefix and then use them.
initialFoldLevel
Type: number
Default value: 6
It should be a number from 1
to 6
.
If it is 1
, <h1>
and all lower-level headings’ corresponding sections in the TOC will be folded initially.
If it is 2
, <h2>
and all lower-level headings’ corresponding sections in the TOC will be folded initially.
And so on.
Note that if it is 6
, everything is unfolded initially since there is no <h7>
.
offsetTop
and offsetBottom
Type of both: number
Unit: px
Default value of both: 0
Neotoc considers the portion of the nearest scroll container(usually the <html>
element) cropped by the screen as its viewport.
This viewport is reflected in the TOC to highlight the relevant area.
You can lower the top edge of this viewport using the offsetTop
option and raise the bottom edge of the viewport using offsetBottom
.
This is useful if you have a fixed header or footer.
Note: Nearest scroll container
Here “nearest scroll container” refers to the nearest scroll container of the element matched by the first part of the io
option.
Tip: If you have fixed header...
If you have a fixed header, to ensure the headings scroll to the correct position, you can use the CSS scroll-margin-top
property on the heading elements.
autoFold
Type: boolean
Default value: false
Whether or not to automatically fold/unfold the TOC based on what is in the viewport.
autoScroll
Type: boolean
Default value: true
Whether or not to automatically scroll the TOC to keep the highlighted portion in view when the nearest scroll container is scrolled.
autoScrollOffset
Type: number
Unit: px
Default value: 50
When autoScroll
is true
, this option allows you to control the distance the highlighted area should stay from the top and bottom edges of the main TOC UI (the element that by default has the class .nt-body
) when possible.
toggleFoldIcon
Type: string
Default value: Chevron down icon(SVG)
This icon is shown at the left of each anchor text on the TOC which has sub anchors underneath it to allow you to fold/unfold it. You can set another SVG icon here with this option.
unfoldableIcon
Type: string
Default value: Dot icon(SVG)
This icon is shown at the left of each anchor text on the TOC which do not have sub anchors underneath it. You can set another SVG icon here with this option.
foldIcon
Type: string
Default value: Unfold less rounded icon(SVG)
This is used on the fold button on the header of the TOC. You can set another SVG icon here with this option.
unfoldIcon
Type: string
Default value: Unfold more rounded icon(SVG)
This is used on the unfold button on the header of the TOC. You can set another SVG icon here with this option.
foldAllIcon
Type: string
Default value: Unfold less double rounded icon(SVG)
This is used on the fold all button on the header of the TOC. You can set another SVG icon here with this option.
unfoldAllIcon
Type: string
Default value: Unfold more double rounded icon (SVG)
This is used on the unfold all button on the header of the TOC. You can set another SVG icon here with this option.
Customizing color schemes
Changing colors of different parts of the TOC UI is as simple as assigning some color value to the following CSS variables, which are defined on the root TOC UI element (with the default class nt-widget
):
--bg
--fg
--bg-anchor-hover
--bg-anchor-active
--bg-sub-anchors
--bg-header-btn
--bg-header-btn-hover
--bg-header-btn-active
--bg-header-btn-disabled
--fg-header-btn-disabled
--border-header-btn
--bg-toggle-fold-btn-hover
--bg-toggle-fold-btn-active
--indent-line
--indent-line-highlight
--fold-indicator-gradient-mid
--light-bar
--light-bar-tip-on-fold
--light-opacity
You can change colors responsively using media queries. Additionally, you can define different colors for light and dark modes using CSS selectors.
Tip
If you want to interactively change these variables and see the result on the page, you can use your browser’s developer tools.
For example if you only want the light bar color to orange while keeping rest of your color scheme the same, add the following in a stylesheet that comes after the color scheme stylesheet:
.nt-widget {--light-bar: orange;}
If you are not statisfied with changing a color or two, you can even create your own color schemes from scratch in the same way and use it instead of modifying an existing color scheme.
Customizing the base style
You can modify various aspects of the base style using the following CSS variables:
--body-height
--relative-font-size
--toggle-fold-btn-width
--indent-line-gap
--indent-line-width
--anchor-padding-block
--anchor-padding-inline
--anchor-border-radius
--padding-left
--light-bar-width
--light-bar-tip-radius
--light-spread-length
--folding-duration
Info
Of these few variables should be in some specific units or types:
--relative-font-size
: It should not have any units. Just plain number.--toggle-fold-btn-width
: It should be inem
.--indent-line-gap
: It should be inpx
.--folding-duration
: It should be of<time>
type.
The rest of the variables can be of any <length>
type.
Of these, --body-height
is the one you will most likely want to modify. It describes the height of body of the TOC UI that is the area except the header of the TOC UI. The default value of this is 400px
. Suppose you want this height to be something dynamic like calc(100vh - 5rem)
. Then add the following in a stylesheet that comes after the base stylesheet of your choice:
.nt-widget {--body-height: calc(100vh - 5rem);}
Tip
If you want to interactively change any of these variables and see the result on the page, you can use your browser’s developer tools.
Found a typo? Other suggestions?
Edit this page on Github
Last modified: 2025-03-24