Neotoc
Neotoc is a table of contents (TOC) generator library designed to make in-page navigation engaging and useful. On mobile, tap the bottom-right button to open the TOC. On desktop, it appears by default.
It’s built for documentation sites and technical blogs, but you’re free to use it anywhere it feels right.
Want to tinker with Neotoc in your browser? Check out this CodePen.
Features
- 🌟 Ultra-smooth: It accurately highlights exactly where you are on the page, resulting in an ultra-smooth user experience.
- 🔄 Auto-scroll: Neotoc keeps itself in sync with your scroll, so you never have to hunt for your position.
- 📂 Foldable: Easily fold and unfold nested sections.
- 🎯 Opinionated: Neotoc focuses on a single DOM structure and convention of style, so it stays efficient and free of bloat.
- 🎨 Ready-made styles: Get styled easily with carefully crafted base styles and color schemes.
- 🛠️ Framework-agnostic: Neotoc is not tied to any particular library or framework, freeing you to use it anywhere DOM exists.
- 🚀 Zero dependencies: Built entirely from scratch, with no external dependencies.
- 🌐 Browser support: Works smoothly across all modern and popular web browsers.
- ⚡ Lightweight: The JavaScript bundle is 4.2KB, with ready-made CSS bundle weighing about 2.1KB (both minified and gzipped).
- ♿ Accessible: Accessible with keyboards, touchscreens and screen readers.
Try it out locally
The create-neotoc
CLI tool is the easiest way experiment with Neotoc in the comfort of your favorite code editor. It spins up a simple Neotoc playground with configurable dummy content, making it super easy to experiment.
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.
To learn how to set it up manually read the following section.
Setup guide
Here you will see how to get Neotoc and its ready-made styles. To learn how to use Neotoc to create the TOC UI jump to Neotoc API section.
Neotoc library
Install it from npm with your favorite package manager:
npm install neotoc@1.0.1pnpm add neotoc@1.0.1yarn add neotoc@1.0.1bun add neotoc@1.0.1
You can also use CDNs like jsDelivr to fetch it. For example:
<scriptsrc="https://cdn.jsdelivr.net/npm/neotoc@1.0.1/dist/index.umd.js"defer></script>
It is recommended to use the exact version to avoid any breaking changes.
Ready-made 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:
<linkhref="https://cdn.jsdelivr.net/npm/neotoc@1.0.1/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 schemes:
- 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:
<linkhref="https://cdn.jsdelivr.net/npm/neotoc@1.0.1/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";
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.
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
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.
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.
autoScrollOffset
Type: number
Unit: px
Default value: 50
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.
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-05-25