Neotoc Documentation
Neotoc creates a TOC(Table of Contents) UI by parsing the DOM inside the browser.
The TOC you see on the right is made with neotoc. If you are on a mobile device, tap the sidebar icon at the bottom right to show/hide it.
Motivation and inspiration
Most docs have a table of contents UI. But I haven’t found one that is really intutive. While scrolling the page up and down, very often the heading title that is highlighted in the TOC is to close what I’m reading, not what it really is.
In my imagination fixing this design issue lead to fluid motion. I thought I would be cool to experience it. So I decided to work on it to turn it into reality.
For many other aspects I took inspiration from different places on the internet. The ones I can think of now are:
- For the architecture of the tree view, I took inspriation from the Github’s “Files” side panel.
- The idea of making font size of heading titles in TOC incrementally smaller orignally came from Gwern Branwen and then from Joshua Comeau’s website.
- The idea to truncate long headings and show ellipsis came from vitepress.dev.
- The idea of auto fold came from tocbot.
Features
Its main features are:
- Fluid highlight of the view and smooth auto scrolling of TOC.
- Both manual and auto folding support.
- Easily stylable with dozens of CSS variables.
- TypeScript support.
- Accessible.
- Zero dependency.
- Performant and Lightweight.
- Framework agnostic. You can use it in vanilla HTML CSS JS setup to frameworks like Next.js. This is doc is made with Next.js.
Appetizer 🥗
Here are some controllers I made for you to easily experience its customizability:
-
--relative-font-size
:94 -
--indent-line-gap
:5px - Turn on/off Auto folding:
- Turn on/off ellipsis for long headings:
There are many more things that you can control. But that’s for the rest of this doc to explore.
Quick start guide: It is just three lines away
Here I’m writing this quick start guide in the context of Vite’s vanilla
template for simplicity.
So after setting up your project, install neotoc
(with the package manager you are using. I’m using pnpm
here cause its my favorite):
pnpm add neotoc
Now in your JavaScript file do the following import
s and call neotoc
like below:
import neotoc from "neotoc";import "./neotoc.css";neotoc({ io: "article >> h* >> aside" });
Neotoc doesn’t ship any CSS with the package to allow you to style it freely. However you don’t have to write it from scratch. Use the stylesheet that I’ve written for this site as a starting point. Copy it form Github and paste it in neotoc.css
(or whatever filename you choose) and tweak it as you like it.
Now to create TOC, we must call neotoc
with the io
(i.e. input output) option. Here you have to give the answer of three questions in the form of CSS selectors seperated by >>
:
- Where should it look for headings?
- Here it is the
article
element.
- Here it is the
- Which headings should it consider for creating the TOC?
- Here it is
h*
, which means headings of all levels.(Though it’s not a valid CSS selector, it’s special shortcut that is allowed here).
- Here it is
- Where should it append the the created TOC?
- Here it is the
aside
element.
- Here it is the
If you didn’t have article
, heading elements in it and aside
, create them in your HTML. And make sure those headings have unique ids. Then you should see the TOC in the aside
element.
Note that to get things work nicely Neotoc expect two things:
- Heading that you look for must have unique ids.
- And those headings should appear in a sensible order. By sensible order, I mean:
- First found heading’s level is considered the largest one. So if you choose
<h2>
as your first heading, you must not use the<h1>
heading later. - There must be no gap between two heading levels when going top to bottom. So, after
<h2>
you should not use the heading<h4>
.
- First found heading’s level is considered the largest one. So if you choose
Now you have good a foundational knowledge about it. Let’s now dive into its API.
Neotoc API
neotoc
package only has a default export. It’s the function that you call to create the toc. Here we will name this function as neotoc
too:
import neotoc from "neotoc";
neotoc
takes a options object and returns a cleaup function.
If you are using neotoc
in React or other library/framework, you will probably need to call or return this function where it is needed. For React for example, the pattern is like below:
useEffect(() => {const removeNeotoc = neotoc({io: "article >> h2,h3,h4,h5,h6 >> aside"});return () => {removeNeotoc();};}, []);
In you have a vanilla HTML CSS JS setup you usually don’t need use the cleanup function.
Now, let’s see all of its options in detail.
Options
io
and to
Type of io
: string
Type of to
: HTMLElement
io
is the only required option. We have already covered it mostly in the Quick Start guide. Let’s cover the rest here.
The last part of the io
can be ommitted if there is a need to provide the corresponding HTML element directly. We can use the to
option for that purpose.
One use case of to
option is that it allows you to easily create a wrapper component for it in React:
function Neotoc({from,className,}: {from: string;className?: string;}) {const toRef = useRef<HTMLDivElement>(null);useEffect(() => {return neotoc({ io: `${from} >> h*`, to: toRef.current! });}, [from]);return <div ref={toRef} className={className}></div>;}
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 on the top left of it.
fillAnchor
Type: (heading: HTMLHeadingElement) => string | Node
Default value: (heading) => heading.textContent
It is used to determine the content of anchors elements in the TOC. If your headings have complex structure or contain elements like <code>
or <i>
like in this doc and you want to show them in the TOC, this option can be useful.
Tip: 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
, long heading’s text is truncated with … instead of wrapping it to the next line. If you hover of the anchor the full text is shown in the browser default tooltip.
Tip: If you clone nodes of your headings to populate the content of anchors in the TOC using fillAnchor
and if your headings have <code>
elements, make sure their position is static
when then are in the TOC, otherwise ellipsis may not appear next to them.
classPrefix
Type: string
Default value: nt-
All the CSS classes assigned to different elements in the TOC UI are by default prefixed with nt-
as an attempt to avoid class name collision.
This the nt-
prefix is already used in class names of other elements of your page, you should set a different value here. And in your CSS for neotoc, you will also have to replace all occurances of nt-
with your chosen prefix.
initialFoldLevel
Type: number
Default value: 6
It should be a number from 1
to 6
.
If it is 1
, <h1>
and all lower level heading’s corresponding sections in the TOC will be folded initially.
If it is 2
, <h2>
and all lower level heading’s 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 nearest scroll container’s(usually it is the <html>
element) visible portion on the screen as its viewport.
This viewport is reflected in the TOC to show highlighted area.
These two options allow you to lower the top of edge of the viewport using the offsetTop
option and raise the bottom edge of the viewport using offsetBottom
.
This is useful if you have fixed header or footer.
Note: In this doc, by nearest scroll container, it is meant the nearest scroll container of the element that is matched by the first part of the io
option.
autoFold
Type: boolean
Default value: false
Whether or not to auto fold/unfold the TOC based on the what is in view.
autoScroll
Type: boolean
Default value: true
Whether or not to automatically scroll the TOC to have the highlighted portion in view when the nearest scroll container is scrolled.
autoScrollOffset
Type: number
Unit: px
Default value: 50
When auto scroll is on, with this you can control the distance the highlighted area should stay away from the top and bottom edge of the TOC UI(the element which by default gets the class .nt-toc-holder
) when it can.
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 top bar 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 top bar 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 top bar 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 top bar of the TOC. You can set another SVG icon here with this option.
Theming 🎨🖌️🌈
I recommend you to copy the style for the TOC that ou see on the right, and modify it based on your need because this is the style that I always try to keep updated and polished as much as I can.
The goal of this section is to make yourself familiar with this stylesheet. It will not be a exhaustive overview. I will only include things that you most likely want to modify.
The stylesheet has two main parts:
SETTINGS
: Things that you will usually want to modify, like colors, and size of stuffs.NEOTOC STYLE
: Styles that define the structure of the TOC and subtle details.
Here I will only discuss about the SETTINGS
. Let’s start with light/dark theme.
How to make light/dark theme?
You have to use CSS variables(aka custom properties) for this. The default class name for the TOC widget is .nt-toc-widget
. You should declare colors for light/dark theme via CSS variables here. In the stylesheet it’s done like below:
:root.light .nt-widget {/* light mode color definitions */}:root.dark .nt-widget {/* dark mode color definitions */}/* styles for medium sized screen and above */@media (min-width: 768px) {:root.light .nt-widget {}:root.dark .nt-widget {}}
Note it’s done in a mobile first approach. Here light/dark theme is determined by whether the .light
or .dark
class is present on <html>
element. Here change the selector to match how you differenciate light/dark theme for your website.
The CSS variable names are descriptive and I hope you will easily figure out where they relate to.
I defined the colors in HSL because, it’s very easy to tweak them. But you can use any other color format of your preference.
Now let’s move on to the sizing of stuffs.
Modifying the sizes of stuffs 📏
Coming soon.
Support ❤️
If you think this provides a good user experience and want this project to succeed, please support it by giving it a ⭐ on Github!