TOC - National Design System

A navigable outline that auto-builds from a page's headings and keeps the reader's current section highlighted as they scroll.

Auto-Populated

Point the TOC at an article with data-toc-source and it builds the list from the headings it finds. Missing heading IDs get slugified automatically so anchors resolve.

Built from #tocSampleArticle h2/h3/h4 headings

Introduction

Overview paragraph describing the page purpose.

Setup

Preparing the environment and installing dependencies.

Requirements

What you need before you begin.

Install

Running the install command.

macOS

Notes for macOS users.

Windows

Notes for Windows users.

Usage

Typical usage patterns.

Support

Where to go for help.

Manual Markup

Author the list yourself when the TOC doesn't mirror a page's headings (custom labels, filtered entries, non-heading anchors). Nest <ul> inside an <li> for any number of sub-levels.

Three-level TOC, written by hand

Built-in Features

Auto-initialization

Activates on any .nds-toc element on the page. No manual wiring required.

Heading-Driven List

Scans the article you point it at and builds the full nested list, slugifying any heading that lacks an id.

Unlimited Depth

Each sub-level picks up its own indent and side rail, so deeply nested sections read clearly without extra markup.

Active-Section Tracking

Highlights the section currently below the sticky nav as the reader scrolls, with the indicator following in real time.

Click-to-Scroll

Clicking a TOC entry smooth-scrolls the target heading into view beneath the nav, updates the URL hash, and respects reduced-motion preferences.

Programmatic Control

Create a single instance or reinitialize all TOCs after injecting new content through the NDS.Toc API.

Usage Guidelines

Live Example

Best Practices

  • Use a TOC on long-form content (policy pages, documentation, guides) where readers benefit from skimming the structure and jumping around
  • Use auto-populate (data-toc-source) whenever the TOC should mirror the article one-to-one. It stays in sync automatically as headings are added, renamed, or removed
  • Use manual markup only when you need labels that differ from the headings, a filtered subset, or anchors that aren't headings
  • Do not place a TOC on short pages where every section is already visible. Use the Drawer for plain navigation or the Stepper for linear multi-step flows instead
  • Place the TOC inside a sideinfo column with nds-sticky so it stays visible as the reader scrolls long content
  • Pick nds-sm or nds-md on the surrounding .nds-sideinfo for compact rails. The default width is tuned for richer sideinfo content, not link lists
  • Keep the TOC to three levels or fewer. Deeper trees produce tight indents that are hard to scan and hint at a page that should be split
  • Set data-toc-levels="h2,h3" to skip h4s if the article uses them for inline emphasis rather than real sub-sections
  • Give every heading a stable, human-readable id. The auto-slugifier is a fallback, not a substitute for author-chosen anchors
  • Author labels should match the heading text. Invent TOC-only names only when the heading is verbose and the rail cannot truncate cleanly

Data Attributes

AttributeDescription
data-toc-sourceCSS selector for the container whose headings should populate the list. Omit for manual markup.
data-toc-levelsComma-separated heading tags to include (default: h2,h3,h4). Use h2 for a flat TOC or h2,h3,h4,h5 for deeper docs.

Modifier Classes

ClassApplied toDescription
nds-lined.nds-drawerRequired. Renders the vertical rail beside sub-lists. .nds-toc sets --drawer-lined-block: 0px internally so the rail extends flush to the block edges of each nested list.

JavaScript API

The NDS.Toc API initializes, re-initializes, and creates TOC instances. Auto-init runs on DOMContentLoaded; call NDS.Toc.reinit() after injecting new TOC markup dynamically.

// ── Initialize all TOCs on the page ───────────────── // Called automatically once. Re-run after injecting new TOC markup. NDS.Toc.init(); NDS.Toc.reinit(); // ── Create a single TOC instance ──────────────────── // Returns the NDSToc instance (with .active, .entries, .destroy(), etc.) const toc = document.querySelector('.nds-toc'); const instance = NDS.Toc.create(toc); // ── Read the currently-active entry ────────────────── instance.active; // { link, li, target } | null instance.entries; // Array of { link, li, target } // ── Manually tear down and re-wire ────────────────── instance.destroy(); // Remove click + scroll listeners, clear state instance.update(); // Recompute active entry from current scroll
Was this page useful?
60% of users said Yes from 2843 Feedbacks