remark-toc

remark plugin to generate a Table of Contents (TOC)

Usage no npm install needed!

<script type="module">
  import remarkToc from 'https://cdn.skypack.dev/remark-toc';
</script>

README

remark-toc

Build Coverage Downloads Size Sponsors Backers Chat

remark plugin to generate a table of contents.

Contents

What is this?

This package is a unified (remark) plugin to generate a table of contents of the document such as the one above.

unified is a project that transforms content with abstract syntax trees (ASTs). remark adds support for markdown to unified. mdast is the markdown AST that remark uses. This is a remark plugin that transforms mdast.

When should I use this?

This project is useful when authors are writing docs in markdown that are sometimes quite long and hence would benefit from automated overviews inside them. It is assumed that headings define the structure of documents and that they can be linked to. When this plugin is used, authors can add a certain heading (say, ## Contents) to documents and this plugin will populate those sections with lists that link to all following sections.

GitHub and similar services automatically add IDs (and anchors that link-to-self) to headings. You can add similar features when combining remark with rehype through remark-rehype after this plugin. Then it’s possible to use the rehype plugins rehype-slug (for IDs on headings) and rehype-autolink-headings (for anchors that link-to-self).

This plugin does not generate a table of contents for the whole document or expose it to other plugins. You can use the underlying mdast utility mdast-util-toc and create a plugin yourself to do that and more.

Install

This package is ESM only. In Node.js (version 12.20+, 14.14+, or 16.0+), install with npm:

npm install remark-toc

In Deno with Skypack:

import remarkToc from 'https://cdn.skypack.dev/remark-toc@8?dts'

In browsers with Skypack:

<script type="module">
  import remarkToc from 'https://cdn.skypack.dev/remark-toc@8?min'
</script>

Use

Say we have the following file, example.md:

# Alpha

## Table of contents

## Bravo

### Charlie

## Delta

And our module, example.js, looks as follows:

import {read} from 'to-vfile'
import {remark} from 'remark'
import remarkToc from 'remark-toc'

main()

async function main() {
  const file = await remark()
    .use(remarkToc)
    .process(await read('example.md'))

  console.log(String(file))
}

Now, running node example yields:

# Alpha

## Table of contents

*   [Bravo](#bravo)

    *   [Charlie](#charlie)

*   [Delta](#delta)

## Bravo

### Charlie

## Delta

API

This package exports no identifiers. The default export is remarkToc.

unified().use(remarkToc[, options])

Generate a table of contents. Looks for a certain heading, removes everything between it and an equal or higher heading, and replaces that with a list representing the document structure, linking to all further headings.

options

Configuration (optional).

options.heading

Pattern text of heading to look for (string, default: 'toc|table[ -]of[ -]contents?'). Wrapped in new RegExp('^(' + options.heading + ') , 'i'), so it’s case-insensitive and matches the whole heading text.

options.skip

Pattern text of headings to exclude from the generated list (string, optional). Wrapped in new RegExp('^(' + options.skip + ') , 'i'), so it’s case-insensitive and matches whole heading texts.

options.maxDepth

Maximum heading depth to include in the generated list (number?, default: 6). This is inclusive: when set to 3, headings with a rank of 3 are included (those with three hashes: ###).

options.tight

Whether to compile list items tightly (boolean?, default: false). The default is to add space around items.

options.ordered

Whether to compile list items as an ordered list (boolean?, default: false). The default is to use an unordered list.

options.prefix

String to prepend before links to headings (string?, default: null, example: 'user-content-'). This is useful when combining remark with rehype through remark-rehype after this plugin, and using rehype-sanitize to prevent DOM clobbering of user generated markdown.

options.parents

Parents (such as block quotes and lists) of headings to include in the generated list (is-compatible test, default: the root node). By default only top level headings are used. Pass ['root', 'blockquote'] to also link to headings in block quotes.

Examples

Example: a different heading

The option heading can be set to search for a different heading. The example from before can be changed to search for different headings like so:

@@ -6,7 +6,7 @@ main()

 async function main() {
   const file = await remark()
-    .use(remarkToc)
+    .use(remarkToc, {heading: 'contents'})
     .process(await read('example.md'))

   console.log(String(file))

…that would search for Contents (case-insensitive) headings.

Example: ordered, tight list

The options ordered and tight can be turned on to change the list. The example from before can be changed to generate a tight, ordered list like so:

@@ -6,7 +6,10 @@ main()

 async function main() {
   const file = await remark()
-    .use(remarkToc)
+    .use(remarkToc, {tight: true, ordered: true})
     .process(await read('example.md'))

   console.log(String(file))

…that would generate the following list:

1.  [Bravo](#bravo)
    1.  [Charlie](#charlie)
2.  [Delta](#delta)

Example: including and excluding headings

The options maxDepth, skip, and parents can be used to include and exclude certain headings from list. The example from before can be changed to generate a tight, ordered list like so:

@@ -6,7 +6,10 @@ main()

 async function main() {
   const file = await remark()
-    .use(remarkToc)
+    .use(remarkToc, {maxDepth: 3, skip: 'delta', parents: ['root', 'listItem']})
     .process(await read('example.md'))

   console.log(String(file))

…that would exclude level 4, 5, and 6 headings, exclude headings of delta (case-insensitive, full match), and include headings directly in a list item.

Example: adding a prefix

The option prefix can set to prepend a string to all links to headings in the generated list:

@@ -6,7 +6,10 @@ main()

 async function main() {
   const file = await remark()
-    .use(remarkToc)
+    .use(remarkToc, {prefix: 'user-content-'})
     .process(await read('example.md'))

   console.log(String(file))

…that would generate the following list:

*   [Bravo](#user-content-bravo)

    *   [Charlie](#user-content-charlie)

*   [Delta](#user-content-delta)

Types

This package is fully typed with TypeScript. It exports an Options type, which specifies the interface of the accepted options.

Compatibility

Projects maintained by the unified collective are compatible with all maintained versions of Node.js. As of now, that is Node.js 12.20+, 14.14+, and 16.0+. Our projects sometimes work with older versions, but this is not guaranteed.

This plugin works with unified version 3+ and remark version 4+.

Security

Use of remark-toc involves user content and changes the tree, so it can open you up for a cross-site scripting (XSS) attack.

Existing nodes are copied into the table of contents. The following example shows how an existing script is copied into the table of contents.

The following markdown:

# Table of Contents

## Bravo<script>alert(1)</script>

## Charlie

Yields:

# Table of Contents

-   [Bravo<script>alert(1)</script>](#bravoscriptalert1script)
-   [Charlie](#charlie)

## Bravo<script>alert(1)</script>

## Charlie

This may become a problem if the markdown is later transformed to rehype (hast) or opened in an unsafe markdown viewer.

Related

Contribute

See contributing.md in remarkjs/.github for ways to get started. See support.md for ways to get help.

This project has a code of conduct. By interacting with this repository, organization, or community you agree to abide by its terms.

License

MIT © Titus Wormer