Table of Contents
The comark/plugins/toc plugin automatically generates a hierarchical table of contents (TOC) from document headings. The TOC is extracted from h2 through h6 elements and stored in the parse result's meta.toc property.
Basic Usage
The Table of Contents plugin is included in comark core package and available under comark/plugins/toc path.
Registering the Plugin
import { parse } from 'comark'
import toc from 'comark/plugins/toc'
const result = await parse(content, {
plugins: [toc()]
})
console.log(result.meta.toc) // Generated table of contents
Default Configuration
By default, the plugin generates a TOC with these settings:
{
depth: 2, // Include h2 and h3 headings
searchDepth: 2 // Search 2 levels deep in nested structures
}
Configuration Options
depth
Controls which heading levels to include in the TOC:
// Include only h2 and h3
toc({ depth: 2 })
// Include h2, h3, and h4
toc({ depth: 3 })
// Include h2, h3, h4, h5, and h6
toc({ depth: 5 })
Valid values: 1 to 5 (maps to heading levels h2-h6)
searchDepth
Controls how deep to search for headings in nested component structures:
// Only search top-level headings
toc({ searchDepth: 1 })
// Search 2 levels deep (default)
toc({ searchDepth: 2 })
// Search 3 levels deep
toc({ searchDepth: 3 })
title
Set a custom title for the TOC:
toc({ title: 'On This Page' })
Examples
Basic TOC Generation
import { parse } from 'comark'
import toc from 'comark/plugins/toc'
const content = `# Document Title
## Introduction
This is the introduction section.
## Features
### Performance
### Flexibility
## Conclusion
`
const result = await parse(content, {
plugins: [toc()]
})
console.log(result.meta.toc)
{
"title": "",
"depth": 2,
"searchDepth": 2,
"links": [
{
"id": "introduction",
"text": "Introduction",
"depth": 2
},
{
"id": "features",
"text": "Features",
"depth": 2,
"children": [
{
"id": "performance",
"text": "Performance",
"depth": 3
},
{
"id": "flexibility",
"text": "Flexibility",
"depth": 3
}
]
},
{
"id": "conclusion",
"text": "Conclusion",
"depth": 2
}
]
}
Using Frontmatter to Control TOC
You can control TOC behavior via frontmatter:
const content = `---
title: My Article
depth: 3
searchDepth: 3
---
# My Article
## Section 1
### Subsection 1.1
#### Deep Heading
## Section 2
`
const result = await parse(content, {
plugins: [toc()]
})
console.log(result.meta.toc.depth) // 3 (from frontmatter)
console.log(result.meta.toc.title) // "My Article" (from frontmatter)
console.log(result.meta.toc.searchDepth) // 3 (from frontmatter)
Vue TOC Component Example
<script setup lang="ts">
import { ref } from 'vue'
import { parse } from 'comark'
import { Comark } from '@comark/vue'
import toc from 'comark/plugins/toc'
const content = ref(`# Guide
## Introduction
## Installation
## Configuration
### Basic Setup
### Advanced Options
`)
const options = {
plugins: [toc({ depth: 3 })]
}
// Parse to get TOC
const result = await parse(content.value, options)
const toc = result.meta.toc
</script>
<template>
<div class="docs-layout">
<!-- Sidebar with TOC -->
<aside class="toc">
<h2>{{ toc.title || 'Table of Contents' }}</h2>
<nav>
<ul>
<li v-for="link in toc.links" :key="link.id">
<a :href="`#${link.id}`">{{ link.text }}</a>
<ul v-if="link.children">
<li v-for="child in link.children" :key="child.id">
<a :href="`#${child.id}`">{{ child.text }}</a>
</li>
</ul>
</li>
</ul>
</nav>
</aside>
<!-- Main content -->
<main>
<Comark :options="options">{{ content }}</Comark>
</main>
</div>
</template>
React TOC Component Example
import { useState, useEffect } from 'react'
import { parse } from 'comark'
import { Comark } from '@comark/react'
import toc from 'comark/plugins/toc'
const content = `# Guide
## Introduction
## Installation
## Configuration
### Basic Setup
### Advanced Options
`
const options = {
plugins: [toc({ depth: 3 })]
}
export default function DocsPage() {
const [toc, setToc] = useState(null)
useEffect(() => {
async function loadToc() {
const result = await parse(content, options)
setToc(result.meta.toc)
}
loadToc()
}, [])
if (!toc) return <div>Loading...</div>
return (
<div className="docs-layout">
{/* Sidebar with TOC */}
<aside className="toc">
<h2>{toc.title || 'Table of Contents'}</h2>
<nav>
<ul>
{toc.links.map(link => (
<li key={link.id}>
<a href={`#${link.id}`}>{link.text}</a>
{link.children && (
<ul>
{link.children.map(child => (
<li key={child.id}>
<a href={`#${child.id}`}>{child.text}</a>
</li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
</aside>
{/* Main content */}
<main>
<Comark options={options}>{content}</Comark>
</main>
</div>
)
}
Recursive TOC Renderer
For deeply nested TOCs:
interface TocLinkProps {
link: {
id: string
text: string
children?: TocLinkProps['link'][]
}
}
function TocLink({ link }: TocLinkProps) {
return (
<li>
<a href={`#${link.id}`}>{link.text}</a>
{link.children && (
<ul>
{link.children.map(child => (
<TocLink key={child.id} link={child} />
))}
</ul>
)}
</li>
)
}
function TableOfContents({ toc }) {
return (
<nav>
<ul>
{toc.links.map(link => (
<TocLink key={link.id} link={link} />
))}
</ul>
</nav>
)
}
API Reference
toc(options?): ComarkPlugin
Creates a table of contents generation plugin.
Parameters:
options(optional) - TOC configuration options
Options:
interface TocOptions {
title?: string // TOC title (default: '')
depth?: number // Heading depth to include: 1-5 (default: 2)
searchDepth?: number // Search depth in nested structures (default: 2)
}
Returns: A Comark plugin that generates table of contents.
TOC Data Structure
interface Toc {
title: string // TOC title
depth: number // Maximum heading depth included
searchDepth: number // Search depth in nested structures
links: TocLink[] // Hierarchical list of links
}
interface TocLink {
id: string // Heading ID (for anchor links)
text: string // Heading text content
depth: number // Heading level (2-6)
children?: TocLink[] // Nested child headings (optional)
}
Features
- Automatic ID Generation: Headings automatically get IDs for anchor links
- Hierarchical Structure: Nested headings create parent-child relationships
- Frontmatter Integration:
title,depth, andsearchDepthcan be set in frontmatter - Flexible Depth Control: Choose which heading levels to include
- Search Depth: Control how deep to search in nested component structures
- Text Extraction: Automatically extracts plain text from headings with formatting
Use Cases
- Documentation Sites - Generate navigation for long documentation pages
- Blog Posts - Create article outlines for reader navigation
- Guides & Tutorials - Help users navigate step-by-step instructions
- API Documentation - Organize method and property listings
- Landing Pages - Create "Jump to Section" navigation
How It Works
The plugin:
- Searches through the AST for heading elements (h2-h6)
- Extracts heading text, IDs, and depth information
- Organizes headings into a hierarchical structure
- Respects the
depthsetting to filter heading levels - Uses
searchDepthto determine how deep to search in nested structures - Stores the result in
tree.meta.toc
Notes
h1headings are excluded from TOC (typically used for page title)- Headings must have an
idattribute to appear in the TOC - The
depthparameter maps to heading levels:1= h2 only,2= h2-h3,3= h2-h4, etc. - Frontmatter values override plugin options
- The TOC is generated during parsing, not at render time
- Works seamlessly with custom components and nested structures
See Also
- Summary Plugin - Extract content summaries
- Emoji Plugin - Convert emoji shortcodes
- Markdown Syntax - Heading syntax reference
- Parse API - Parsing markdown content