import { TableOfContents } from "@cloudflare/kumo";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
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>
);
}安装
桶式导出
import { TableOfContents } from "@cloudflare/kumo";细粒度导入
import { TableOfContents } from "@cloudflare/kumo/components/table-of-contents";用法
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>
);
}该组件纯为展示用途,每个项目的 active 状态由你控制。需要自动滚动跟踪时,可与下面的
useTableOfContentsActiveId hook 搭配使用。
滚动跟踪
useTableOfContentsActiveId 会依据滚动位置推导当前章节:按文档顺序传入各章节
id,它会返回可视区域内最靠上的章节 id,并提供 selectSection action,在平滑滚动
落地之前把所点击的章节固定为激活状态(这样跳转后短章节也能保持高亮)。# 形式的
深链接(如 #usage)会自动处理。该 hook 兼容 SSR,所有 DOM 访问都在 effect 中完成。
import { TableOfContents, useTableOfContentsActiveId } from "@cloudflare/kumo";
export default function Example({ headings }) {
const { activeId, selectSection } = useTableOfContentsActiveId({
ids: headings.map((h) => h.slug),
offset: 64, // fixed header height in px
});
return (
<TableOfContents>
<TableOfContents.Title>On this page</TableOfContents.Title>
<TableOfContents.List>
{headings.map((h) => (
<TableOfContents.Item
key={h.slug}
href={`#${h.slug}`}
active={activeId === h.slug}
onClick={() => selectSection(h.slug)}
>
{h.text}
</TableOfContents.Item>
))}
</TableOfContents.List>
</TableOfContents>
);
}传入 root 元素可跟踪自定义滚动容器,而非视口(当哈希导航不适用时,可传
trackHash: false):
Overview
Scrollable placeholder copy for the Overview section. Scrollable placeholder copy for the Overview section. Scrollable placeholder copy for the Overview section. Scrollable placeholder copy for the Overview section. Scrollable placeholder copy for the Overview section. Scrollable placeholder copy for the Overview section.
Installation
Scrollable placeholder copy for the Installation section. Scrollable placeholder copy for the Installation section. Scrollable placeholder copy for the Installation section. Scrollable placeholder copy for the Installation section. Scrollable placeholder copy for the Installation section. Scrollable placeholder copy for the Installation section.
Usage
Scrollable placeholder copy for the Usage section. Scrollable placeholder copy for the Usage section. Scrollable placeholder copy for the Usage section. Scrollable placeholder copy for the Usage section. Scrollable placeholder copy for the Usage section. Scrollable placeholder copy for the Usage section.
API
Scrollable placeholder copy for the API section. Scrollable placeholder copy for the API section. Scrollable placeholder copy for the API section. Scrollable placeholder copy for the API section. Scrollable placeholder copy for the API section. Scrollable placeholder copy for the API section.
import { useState } from "react";
import { TableOfContents, useTableOfContentsActiveId } from "@cloudflare/kumo";
/**
* Live scroll tracking via `useTableOfContentsActiveId`, scoped to a custom
* scroll container through the `root` option. Scroll the content — the active
* item follows; click an item to jump.
*/
export function TableOfContentsScrollspyDemo() {
const [root, setRoot] = useState<HTMLDivElement | null>(null);
const { activeId, selectSection } = useTableOfContentsActiveId({
ids: scrollspySections.map((s) => s.id),
root,
trackHash: false,
});
return (
<div className="flex w-full max-w-xl gap-6">
<div className="min-w-40">
<TableOfContents>
<TableOfContents.Title>On this page</TableOfContents.Title>
<TableOfContents.List>
{scrollspySections.map((section) => (
<TableOfContents.Item
key={section.id}
render={<button type="button" />}
active={activeId === section.id}
onClick={() => {
selectSection(section.id);
root
?.querySelector(`#${section.id}`)
?.scrollIntoView({ behavior: "smooth", block: "start" });
}}
>
{section.title}
</TableOfContents.Item>
))}
</TableOfContents.List>
</TableOfContents>
</div>
<div
ref={setRoot}
className="h-64 flex-1 overflow-y-auto rounded-lg border border-kumo-hairline p-4"
>
{scrollspySections.map((section) => (
<section key={section.id}>
<h4
id={section.id}
className="mb-2 scroll-mt-2 text-sm font-semibold"
>
{section.title}
</h4>
<p className="mb-6 text-sm text-kumo-subtle">
{Array.from(
{ length: 6 },
() =>
`Scrollable placeholder copy for the ${section.title} section. `,
).join("")}
</p>
</section>
))}
<div className="h-40" />
</div>
</div>
);
}选项
ids —— 章节锚点 id,按文档顺序排列(必填)。offset —— 从视口/根元素顶部到
激活线的像素距离,通常等于固定页头的高度(默认 0)。root —— 自定义滚动容器
(默认:视口)。trackHash —— 加载时及 hashchange 时依据 location.hash 选择
章节(默认 true)。
示例
交互式
点击项目即可将其设为激活状态。状态由使用方通过 active 与 onClick 控制。
import { useState } from "react";
import { TableOfContents } from "@cloudflare/kumo";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
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>
);
}无激活项目
当没有任何项目设置 active 时,所有项目都会显示默认的弱化文字样式,并带有悬停指示。
import { TableOfContents } from "@cloudflare/kumo";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
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>
);
}分组
使用 TableOfContents.Group 可以将项目组织进带标签的分区,子项目会缩进显示。
分组支持两种模式:传入 href 让分组标签成为可点击链接(如下面的「示例」与「API」),
或省略 href 作为纯静态标题(如「快速开始」)。
import { TableOfContents } from "@cloudflare/kumo";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
/** Shows both group modes: clickable group labels (with `href`) and plain title labels (without `href`). */
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="Examples" href="#examples-demo">
<TableOfContents.Item className="cursor-pointer">
Basic example
</TableOfContents.Item>
<TableOfContents.Item className="cursor-pointer">
Advanced example
</TableOfContents.Item>
</TableOfContents.Group>
<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" href="#api-demo">
<TableOfContents.Item className="cursor-pointer">
Props
</TableOfContents.Item>
<TableOfContents.Item className="cursor-pointer">
Events
</TableOfContents.Item>
</TableOfContents.Group>
</TableOfContents.List>
</TableOfContents>
</DemoWrapper>
);
}无标题
标题子组件是可选的,不需要标题时可直接使用 TableOfContents.List。
import { TableOfContents } from "@cloudflare/kumo";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
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>
);
}自定义元素
使用 render 属性可将默认的锚点替换为按钮、路由链接或任意元素。
import { useState } from "react";
import { TableOfContents } from "@cloudflare/kumo";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
/** 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
import Link from "next/link";
<TableOfContents.Item render={<Link href="/intro" />} active>
Introduction
</TableOfContents.Item>;按钮(无导航)
<TableOfContents.Item render={<button type="button" />} onClick={handleClick}>
Introduction
</TableOfContents.Item>API 参考
TableOfContents
根导航容器,默认 aria-label 为 “Table of contents”。
| Prop | Type | Default |
|---|---|---|
| children | ReactNode | - |
| className | string | - |
| id | string | - |
| lang | string | - |
| title | string | - |
TableOfContents.Title
列表上方可选的大写标题(渲染为 <p>)。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
TableOfContents.List
带左侧边框竖线的列表容器。
| Prop | Type | Default |
|---|
No component-specific props. Accepts standard HTML attributes.
TableOfContents.Item
单个导航链接。为当前章节设置 active。使用 render 属性可将锚点替换为路由链接
或按钮。
| Prop | Type | Default | Description |
|---|---|---|---|
| active | boolean | - | Whether this item represents the currently active section. |
| render | React.ReactElement | - | Custom element to render as the link. Use this to integrate with framework routers (e.g., Next.js `<Link>`, React Router `<NavLink>`). The element receives all anchor props including `href`, `className`, and `children`. |
TableOfContents.Group
将项目归入带标签的分区,子项目缩进显示。传入 href 可将标签变为可点击链接,省略则作为纯标题。
| Prop | Type | Default | Description |
|---|---|---|---|
| label* | string | - | Label displayed above the group's items. |
| href | string | - | URL the group label links to. When provided, the label renders as a clickable link with item styling. |
| active | boolean | - | Whether this group's label represents the currently active section. Only applies when `href` is provided. |