Table of Contents
@cloudflare/kumo
import { TableOfContents } from "@cloudflare/kumo";

export function TableOfContentsBasicDemo() {
  return (
    <TableOfContents>
      <TableOfContents.Title>On this page</TableOfContents.Title>
      <TableOfContents.List>
        {headings.map((heading) => (
          <TableOfContents.Item
            key={heading.text}
            active={heading.text === "Usage"}
            className="cursor-pointer"
          >
            {heading.text}
          </TableOfContents.Item>
        ))}
      </TableOfContents.List>
    </TableOfContents>
  );
}

Installation

Barrel

import { TableOfContents } from "@cloudflare/kumo";

Granular

import { TableOfContents } from "@cloudflare/kumo/components/table-of-contents";

Usage

import { TableOfContents } from "@cloudflare/kumo";

export default function Example() {
  return (
    <TableOfContents>
      <TableOfContents.Title>On this page</TableOfContents.Title>
      <TableOfContents.List>
        <TableOfContents.Item href="#intro" active>
          Introduction
        </TableOfContents.Item>
        <TableOfContents.Item href="#api">API Reference</TableOfContents.Item>
      </TableOfContents.List>
    </TableOfContents>
  );
}

This component is purely presentational. All interaction logic — scroll tracking, IntersectionObserver, active state management — is left to the consumer.

Examples

Interactive

Click an item to set it as active. The consumer controls state via active and onClick.

import { useState } from "react";
import { TableOfContents } from "@cloudflare/kumo";

export function TableOfContentsInteractiveDemo() {
  const [active, setActive] = useState("Introduction");

  return (
    <TableOfContents>
      <TableOfContents.Title>On this page</TableOfContents.Title>
      <TableOfContents.List>
        {headings.map((heading) => (
          <TableOfContents.Item
            key={heading.text}
            active={heading.text === active}
            onClick={() => setActive(heading.text)}
            className="cursor-pointer"
          >
            {heading.text}
          </TableOfContents.Item>
        ))}
      </TableOfContents.List>
    </TableOfContents>
  );
}

No active item

When no item has active set, all items show the default subtle text style with a hover indicator.

import { TableOfContents } from "@cloudflare/kumo";

export function TableOfContentsNoActiveDemo() {
  return (
    <TableOfContents>
      <TableOfContents.Title>On this page</TableOfContents.Title>
      <TableOfContents.List>
        {headings.map((heading) => (
          <TableOfContents.Item key={heading.text} className="cursor-pointer">
            {heading.text}
          </TableOfContents.Item>
        ))}
      </TableOfContents.List>
    </TableOfContents>
  );
}

Groups

Use TableOfContents.Group to organize items into labeled sections with indented children.

import { TableOfContents } from "@cloudflare/kumo";

export function TableOfContentsGroupDemo() {
  return (
    <TableOfContents>
      <TableOfContents.Title>On this page</TableOfContents.Title>
      <TableOfContents.List>
        <TableOfContents.Item active className="cursor-pointer">
          Overview
        </TableOfContents.Item>
        <TableOfContents.Group label="Getting Started">
          <TableOfContents.Item className="cursor-pointer">
            Installation
          </TableOfContents.Item>
          <TableOfContents.Item className="cursor-pointer">
            Configuration
          </TableOfContents.Item>
        </TableOfContents.Group>
        <TableOfContents.Group label="API">
          <TableOfContents.Item className="cursor-pointer">
            Props
          </TableOfContents.Item>
          <TableOfContents.Item className="cursor-pointer">
            Events
          </TableOfContents.Item>
        </TableOfContents.Group>
      </TableOfContents.List>
    </TableOfContents>
  );
}

Without title

The title sub-component is optional — use TableOfContents.List directly if you don’t need a heading.

import { TableOfContents } from "@cloudflare/kumo";

export function TableOfContentsWithoutTitleDemo() {
  return (
    <TableOfContents>
      <TableOfContents.List>
        {headings.slice(0, 3).map((heading) => (
          <TableOfContents.Item
            key={heading.text}
            active={heading.text === "Introduction"}
            className="cursor-pointer"
          >
            {heading.text}
          </TableOfContents.Item>
        ))}
      </TableOfContents.List>
    </TableOfContents>
  );
}

API Reference

PropTypeDefault
childrenReactNode-
classNamestring-
idstring-
langstring-
titlestring-