Skip to main content

Overview

The Table of Contents plugin automatically generates a table of contents based on heading blocks in your document. It provides navigation links to headings and updates dynamically as content changes.

Installation

npm install @yoopta/table-of-contents

Basic Usage

import { useMemo } from 'react';
import YooptaEditor, { createYooptaEditor } from '@yoopta/editor';
import TableOfContents from '@yoopta/table-of-contents';
import { HeadingOne, HeadingTwo, HeadingThree } from '@yoopta/headings';

const plugins = [HeadingOne, HeadingTwo, HeadingThree, TableOfContents];

export default function Editor() {
  const editor = useMemo(() => createYooptaEditor({ plugins, marks: [] }), []);
  return <YooptaEditor editor={editor} onChange={() => {}} />;
}

Features

  • Auto-Generation: Automatically extracts headings from document
  • Multi-Level: Support for H1, H2, H3 (configurable depth)
  • Live Updates: Updates in real-time as headings change
  • Navigation: Click to scroll to heading
  • Numbered Lists: Optional numbering (1. 2. 3.)
  • Collapsible: Optional collapsible TOC block
  • Custom Title: Set custom title (e.g., “Contents”, “On this page”)
  • Shortcuts: Type toc or table-of-contents to insert

Configuration

import TableOfContents from '@yoopta/table-of-contents';

const plugins = [TableOfContents];

Options

display
object
shortcuts
string[]
default:"['toc', 'table-of-contents']"
Keyboard shortcuts to trigger the plugin in slash menu

Element Props

table-of-contents.props
object

Commands

Table of Contents commands are available via TableOfContentsCommands.

insertTableOfContents

Insert a new Table of Contents block.
import TableOfContents, { TableOfContentsCommands } from '@yoopta/table-of-contents';

TableOfContentsCommands.insertTableOfContents(editor, {
  props: {
    depth: 2,
    title: 'On this page',
    showNumbers: true,
    headingTypes: ['HeadingOne', 'HeadingTwo'],
    collapsible: false,
  },
  at: 0,
  focus: false,
});
props.depth
1 | 2 | 3
default:"3"
Maximum heading level
props.title
string
Custom title for the TOC
props.showNumbers
boolean
default:"false"
Show numbered list
props.headingTypes
string[]
Block types to include as headings
props.collapsible
boolean
Make TOC collapsible
at
YooptaPathIndex
Index where to insert the block
focus
boolean
Whether to focus after insert

updateTableOfContents

Update TOC properties.
import { TableOfContentsCommands } from '@yoopta/table-of-contents';

TableOfContentsCommands.updateTableOfContents(editor, blockId, {
  depth: 2,
  showNumbers: true,
});

deleteTableOfContents

Delete a TOC block.
import { TableOfContentsCommands } from '@yoopta/table-of-contents';

TableOfContentsCommands.deleteTableOfContents(editor, blockId);

buildTableOfContentsElements

Build TOC element structure (used internally).
import { TableOfContentsCommands } from '@yoopta/table-of-contents';

const tocElement = TableOfContentsCommands.buildTableOfContentsElements(editor, {
  props: { depth: 2 },
});

Hooks

useTableOfContentsItems

Hook for accessing TOC items and building custom TOC UI.
import { useTableOfContentsItems } from '@yoopta/table-of-contents';

function CustomTOC({ editor, blockId }) {
  const { items, loading, scrollToHeading } = useTableOfContentsItems(editor, {
    blockId,
    depth: 2,
    headingTypes: ['HeadingOne', 'HeadingTwo'],
  });

  return (
    <nav>
      <h3>Contents</h3>
      <ul>
        {items.map((item) => (
          <li
            key={item.id}
            style={{ marginLeft: `${item.level * 20}px` }}
            onClick={() => scrollToHeading(item.id)}
          >
            {item.text}
          </li>
        ))}
      </ul>
    </nav>
  );
}
Hook Options:
blockId
string
ID of the TOC block
depth
1 | 2 | 3
Maximum heading level to include
headingTypes
string[]
Block types to treat as headings
Hook Return:
type UseTableOfContentsItemsReturn = {
  items: TableOfContentsItem[];
  loading: boolean;
  scrollToHeading: (headingId: string) => void;
};

type TableOfContentsItem = {
  id: string;
  text: string;
  level: number; // 1, 2, or 3
  blockId: string;
};

Types & Constants

Heading Type Levels

import { HEADING_TYPE_LEVEL } from '@yoopta/table-of-contents';

// Maps heading block types to levels
HEADING_TYPE_LEVEL = {
  HeadingOne: 1,
  HeadingTwo: 2,
  HeadingThree: 3,
};

Default Heading Types

import { DEFAULT_HEADING_TYPES } from '@yoopta/table-of-contents';

// Default heading types to include
DEFAULT_HEADING_TYPES = ['HeadingOne', 'HeadingTwo', 'HeadingThree'];

Usage Examples

Basic TOC

TableOfContentsCommands.insertTableOfContents(editor, {
  props: {
    depth: 3,
    title: 'Table of Contents',
  },
  at: 0,
});

TOC with Numbers

TableOfContentsCommands.insertTableOfContents(editor, {
  props: {
    depth: 2,
    title: 'Contents',
    showNumbers: true,
  },
  at: 0,
});

Custom Heading Types

TableOfContentsCommands.insertTableOfContents(editor, {
  props: {
    depth: 2,
    headingTypes: ['CustomHeading1', 'CustomHeading2'],
    title: 'Page Outline',
  },
  at: 0,
});

Collapsible TOC

TableOfContentsCommands.insertTableOfContents(editor, {
  props: {
    depth: 3,
    title: 'On this page',
    collapsible: true,
  },
  at: 0,
});

Parsers

HTML

  • Deserialize: <nav> or <div> with TOC structure
  • Serialize: TOC block → <nav> with nested list of heading links

Email

  • Serialize: Table-based layout with TOC links for email clients

Styling

The TOC plugin provides structure; you can style it with CSS:
/* Example TOC styles */
.toc-container {
  padding: 20px;
  background: #f8f9fa;
  border-radius: 8px;
}

.toc-title {
  font-size: 18px;
  font-weight: bold;
  margin-bottom: 12px;
}

.toc-list {
  list-style: none;
  padding: 0;
}

.toc-item {
  padding: 4px 0;
  cursor: pointer;
}

.toc-item:hover {
  color: #007bff;
}

.toc-item.level-1 {
  margin-left: 0;
}

.toc-item.level-2 {
  margin-left: 20px;
}

.toc-item.level-3 {
  margin-left: 40px;
}

Best Practices

Placement

Place TOC at the beginning of long documents:
// Insert at the start
TableOfContentsCommands.insertTableOfContents(editor, {
  at: 0,
  props: { depth: 2, title: 'Contents' },
});

Depth Selection

Use appropriate depth based on document structure:
  • Short articles: depth: 2 (H1, H2)
  • Documentation: depth: 3 (H1, H2, H3)
  • Landing pages: depth: 1 (H1 only)

Custom Headings

If you have custom heading plugins:
TableOfContentsCommands.insertTableOfContents(editor, {
  props: {
    headingTypes: ['Title', 'Subtitle', 'Section'],
  },
});

Build docs developers (and LLMs) love