page-toc
Table of contents with scroll-spy highlighting. Auto-generates from headings or enhances existing manual markup.
Overview
The <page-toc> component creates a table of contents that highlights the current section as you scroll. It supports two modes:
- Auto-generated: Scans the page for headings and builds the ToC automatically
- Manual markup: Enhances existing navigation with scroll-spy (progressive enhancement)
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
levels |
string | h2,h3 |
Comma-separated heading levels to include |
scope |
string | main |
CSS selector for content scope to scan |
title |
string | On this page |
Title shown above the ToC |
Excluding Headings
Headings inside live examples or embedded content can pollute the table of contents. Add data-toc-ignore to exclude them.
On an ancestor container
All headings inside the marked container are skipped. This is the most common pattern for doc pages with inline examples:
<div class="example" data-toc-ignore> <h2>Demo Heading</h2> <p>This heading will not appear in the ToC.</p></div>
On a single heading
Exclude an individual heading while keeping its siblings:
<h2 data-toc-ignore>This heading is skipped</h2><h2>This heading appears in the ToC</h2>
Auto-Generated Mode
When the component is empty, it automatically scans the page for headings (with IDs) and builds the table of contents.
<!-- Auto-generates ToC from page headings --><page-toc></page-toc> <!-- With custom options --><page-toc levels="h2,h3,h4" scope="article" title="Contents"></page-toc>
Requirements
- Headings must have
idattributes (use heading-links to auto-generate) - Headings must be within the
scopecontainer (default:main)
Manual Markup Mode
For progressive enhancement, provide your own markup. The component will add scroll-spy behavior without modifying your structure.
<!-- Progressive enhancement: works without JS --><page-toc> <details class="details" open> <summary class="summary">On this page</summary> <nav class="nav" aria-label="On this page"> <ul class="list"> <li class="item"> <a href="#overview" class="link">Overview</a> </li> <li class="item" data-level="1"> <a href="#installation" class="link">Installation</a> </li> <li class="item"> <a href="#usage" class="link">Usage</a> </li> <li class="item" data-level="1"> <a href="#examples" class="link">Examples</a> </li> </ul> </nav> </details></page-toc>
CSS Classes
| Class | Purpose |
|---|---|
.details |
Collapsible container |
.summary |
ToC header/title |
.nav |
Navigation wrapper |
.list |
Links list |
.item |
List item (use data-level for nesting) |
.link |
Navigation link |
.active |
Applied to current section link (auto-managed) |
Scroll-Spy Behavior
The component uses IntersectionObserver to track which section is currently visible and highlights the corresponding link.
- The first visible heading in the viewport is highlighted
- Links receive the
.activeclass andaria-current="true" - URL hash is updated when clicking links
- Syncs with browser back/forward navigation
Responsive Behavior
The component adapts to different screen sizes:
Wide Screens (1024px+)
- ToC is always visible
- Position: sticky for sidebar placement
- Full heading hierarchy shown
Narrow Screens
- Collapsible disclosure pattern
- Nested headings hidden (except active)
- Click to expand/collapse
Sidebar Layout
Common pattern: ToC in a sidebar alongside main content.
<layout-sidebar data-layout-side="right" data-width="250px"> <article> <h1>Page Title</h1> <section id="overview"> <h2>Overview</h2> <p>Content...</p> </section> <section id="usage"> <h2>Usage</h2> <p>Content...</p> </section> </article> <aside> <page-toc levels="h2,h3"></page-toc> </aside></layout-sidebar>
Events
| Event | Detail | Description |
|---|---|---|
page-toc:navigate |
{ id: string } |
Fired when a ToC link is clicked |
const toc = document.querySelector('page-toc'); toc.addEventListener('page-toc:navigate', (event) => { console.log('Navigated to:', event.detail.id);});
JavaScript API
| Method | Description |
|---|---|
refresh() |
Rebuild ToC after dynamic content changes |
Accessibility
- Uses semantic
<nav>element witharia-label - Active link marked with
aria-current="true" - Keyboard accessible - tab to navigate, enter to activate
- Focus moves to heading after navigation
- Respects
prefers-reduced-motionfor scroll behavior
Styling with CSS Custom Properties
The component uses scoped styles via @scope. Override these properties for customization:
--color-interactive- Active link color--color-text-muted- Inactive link color--color-surface-raised- Hover background--border-width-medium- Active link border
Best Practices
- Use with heading-links to ensure headings have IDs
- Keep ToC visible on wide screens for easy navigation
- Use
levels="h2,h3"for most documentation pages - Add
data-toc-ignoreto example containers that include headings - Consider manual markup for static sites (better initial render)
Related
- heading-links - Auto-generates heading IDs and anchor links
- layout-sidebar - Sidebar layout for ToC placement
- nav - Navigation landmark element
- details - Collapsible disclosure (used internally)