import { TableOfContents } from "@cloudflare/kumo";
export function TableOfContentsBasicDemo() {
return (
<DemoWrapper>
<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>
</DemoWrapper>
);
} 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 (
<DemoWrapper>
<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>
</DemoWrapper>
);
} 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 (
<DemoWrapper>
<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>
</DemoWrapper>
);
} Groups
Use TableOfContents.Group to organize items into labeled sections with
indented children.
import { TableOfContents } from "@cloudflare/kumo";
export function TableOfContentsGroupDemo() {
return (
<DemoWrapper>
<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>
</DemoWrapper>
);
} 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 (
<DemoWrapper>
<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>
</DemoWrapper>
);
} Custom element
Use the render prop to swap the default anchor for a button, router link, or
any element.
import { useState } from "react";
import { TableOfContents } from "@cloudflare/kumo";
/** Demonstrates using the `render` prop with a custom link component. */
export function TableOfContentsRenderPropDemo() {
const [clicked, setClicked] = useState<string | null>(null);
return (
<DemoWrapper>
<div className="space-y-3">
<TableOfContents>
<TableOfContents.List>
{["Introduction", "Installation", "Usage"].map((text) => (
<TableOfContents.Item
key={text}
render={<button type="button" />}
onClick={() => setClicked(text)}
active={text === "Introduction"}
>
{text}
</TableOfContents.Item>
))}
</TableOfContents.List>
</TableOfContents>
{clicked && (
<p className="text-xs text-kumo-subtle">Clicked: {clicked}</p>
)}
</div>
</DemoWrapper>
);
} // React Router
<TableOfContents.Item render={<Link to="/intro" />} active>
Introduction
</TableOfContents.Item>
// Next.js
<TableOfContents.Item render={<Link href="/intro" />} active>
Introduction
</TableOfContents.Item>
// Button (no navigation)
<TableOfContents.Item render={<button type="button" />} onClick={handleClick}>
Introduction
</TableOfContents.Item>With Next.js
import Link from "next/link";
<TableOfContents.Item render={<Link />} href="/intro" active>
Introduction
</TableOfContents.Item>; API Reference
| Prop | Type | Default |
|---|---|---|
| children | ReactNode | - |
| className | string | - |
| id | string | - |
| lang | string | - |
| title | string | - |