# How 30 Seconds of Code Generates Table of Contents for Articles

> Learn how the 30 seconds of code project automatically generates tables of contents by parsing HTML with its TocReader class. Get structured article navigation effortlessly.

- Repository: [Angelos Chalaris/30-seconds-of-code](https://github.com/Chalarangelo/30-seconds-of-code)
- Tags: internals
- Published: 2026-02-25

---

**The 30 Seconds of Code project automatically generates tables of contents by parsing rendered HTML with a custom `TocReader` class that extracts heading levels, nests them hierarchically, and converts them into semantic markup.**

The **30 Seconds of Code** repository is a popular collection of short JavaScript snippets and coding articles. To help readers navigate lengthy documentation, the project implements an **automatic table of contents generation** system that transforms markdown headings into interactive navigation trees without requiring authors to manually markup their content.

## Parsing Headings from Rendered HTML

The TOC generation process operates on **rendered HTML** rather than raw markdown. In [`src/lib/contentUtils/tocReader.js`](https://github.com/Chalarangelo/30-seconds-of-code/blob/main/src/lib/contentUtils/tocReader.js), the `TocReader` class defines a regular expression matcher that identifies `h2` through `h4` elements containing an anchor tag with an `id` attribute.

The matcher extracts three critical pieces of data from each heading:

- **Level**: The heading rank (2, 3, or 4)
- **Anchor ID**: The `id` attribute of the nested `a` tag, used for fragment-link `href` values
- **Text**: The plain text content of the heading

This extraction produces a flat array of heading objects that serve as the raw material for the hierarchical TOC structure.

## Nesting Headings and Rendering the TOC Structure

Once the flat list is extracted, `TocReader.nestHeadings` recursively groups headings into a tree structure based on their levels. An `h4` element becomes a child of the preceding `h3`, which itself nests under its parent `h2`.

The `TocReader.toNavItem` method transforms each node into HTML markup:

- An `li` element containing an `a` with `href="#{id}"` linking to the heading's anchor
- When children exist, an inner `ol` element containing the nested sub-list

The public entry point `TocReader.readToC` orchestrates the entire process. It returns a string containing a custom `table-of-contents` element wrapping an ordered list, or `undefined` if no qualifying headings are found.

## Integration with the Snippet Processing Pipeline

The TOC generation hooks into the content pipeline at [`src/lib/contentUtils/modelWorkers/snippet.js`](https://github.com/Chalarangelo/30-seconds-of-code/blob/main/src/lib/contentUtils/modelWorkers/snippet.js). After converting markdown to HTML, the worker checks the `tocEnabled` boolean flag (which defaults to `true` in front-matter) to determine whether to generate a TOC for that specific article.

The implementation passes the rendered `fullDescriptionHtml` to `TocReader.readToC`:

```javascript
const tableOfContentsHtml = tocEnabled
  ? TocReader.readToC(fullDescriptionHtml) || ''
  : '';

```

The resulting HTML string is stored in the `tableOfContentsHtml` field of the snippet model and later exported as the `tableOfContents` property available to templates.

## Practical Code Examples

### Using TocReader Directly

```javascript
import TocReader from '#src/lib/contentUtils/tocReader.js';

// articleHtml is the rendered HTML content from markdown
const tocHtml = TocReader.readToC(articleHtml);

if (tocHtml) {
  console.log('Generated TOC:', tocHtml);
} else {
  console.log('No headings found – TOC not generated');
}

```

### Integrating TOC Generation in a Content Worker

```javascript
import TocReader from '#src/lib/contentUtils/tocReader.js';
import MarkdownParser from '#src/lib/contentUtils/markdownParser/markdownParser.js';

async function buildSnippet(snippet) {
  const fullDescriptionHtml = await MarkdownParser.parse(
    snippet.fullText, 
    snippet.language
  );

  const toc = snippet.tocEnabled
    ? TocReader.readToC(fullDescriptionHtml) || ''
    : '';

  return {
    ...snippet,
    fullDescriptionHtml,
    tableOfContentsHtml: toc,
  };
}

```

### Expected TOC Markup Output

```html
<table-of-contents>
  <ol>
    <li><a href="#introduction">Introduction</a></li>
    <li>
      <a href="#usage">Usage</a>
      <ol>
        <li><a href="#basic-example">Basic Example</a></li>
        <li><a href="#advanced-options">Advanced Options</a></li>
      </ol>
    </li>
    <li><a href="#reference">Reference</a></li>
  </ol>
</table-of-contents>

```

## Summary

- The **30 Seconds of Code** project generates TOCs from rendered HTML using the `TocReader` class in [`src/lib/contentUtils/tocReader.js`](https://github.com/Chalarangelo/30-seconds-of-code/blob/main/src/lib/contentUtils/tocReader.js).
- The parser uses regular expressions to extract `h2` through `h4` headings with anchor IDs, then recursively nests them into a hierarchical tree.
- The `TocReader.readToC` method returns a `table-of-contents` custom element containing nested ordered lists, or `undefined` if no headings exist.
- The snippet worker in [`src/lib/contentUtils/modelWorkers/snippet.js`](https://github.com/Chalarangelo/30-seconds-of-code/blob/main/src/lib/contentUtils/modelWorkers/snippet.js) integrates this logic, respecting the `tocEnabled` front-matter flag to conditionally generate TOCs for each article.

## Frequently Asked Questions

### How does the TOC generator handle deeply nested headings?

The `TocReader.nestHeadings` method recursively groups headings based on their level, allowing `h4` elements to nest under `h3` headings, which themselves nest under `h2` headings. The algorithm constructs a tree structure that preserves the document hierarchy regardless of nesting depth, though the parser specifically targets `h2` through `h4` elements and ignores `h1` and deeper levels.

### Can authors disable the table of contents for specific articles?

Yes, authors can control TOC generation through front-matter metadata. The snippet processor checks the `tocEnabled` boolean flag (which defaults to `true`) in each article's front-matter. When set to `false`, the `TocReader.readToC` method is skipped and an empty string is assigned to the `tableOfContentsHtml` field, effectively disabling the TOC for that specific snippet.

### Why does the TOC generator parse HTML instead of Markdown?

The generator operates on rendered HTML because the 30 Seconds of Code pipeline converts Markdown to HTML early in the processing chain. Parsing HTML allows the `TocReader` to work with normalized, rendered content where heading anchors have already been generated, ensuring that the TOC links match the actual DOM structure. This approach also handles any Markdown extensions or plugins that might modify heading output during the HTML conversion phase.

### What HTML structure does the generated TOC produce?

The `TocReader.readToC` method returns a string containing a custom `table-of-contents` element wrapping a nested `ol` structure. Each heading becomes an `li` containing an `a` with `href` linking to the heading's anchor ID. When headings have child elements, the method generates nested `ol` lists inside the parent `li`, creating a hierarchical navigation tree that reflects the document structure.