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, // 固定头部高度 (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 —— 从视口/root
顶部到激活线的像素距离,通常为固定头部的高度 (默认 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 可让分组标签成为
可点击的链接 (如下方的 “Examples” 和 “API”),省略则为
普通的非交互标题 (如 “Getting Started”)。
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>;Button(不导航)
<TableOfContents.Item render={<button type="button" />} onClick={handleClick}>
Introduction
</TableOfContents.Item>API 参考
TableOfContents
根导航容器,默认 aria-label 为 “Table of contents”。
| 属性 | 类型 | 默认值 |
|---|---|---|
| children | ReactNode | - |
| className | string | - |
| id | string | - |
| lang | string | - |
| title | string | - |
TableOfContents.Title
列表上方可选的大写标题 (渲染为 <p>)。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专属属性,接受标准 HTML 属性。
TableOfContents.List
带左侧边线的列表容器。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专属属性,接受标准 HTML 属性。
TableOfContents.Item
单个导航链接。用 active 标记当前章节。用 render
属性可把锚点换成路由链接或按钮。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| 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 可让标签成为可点击的链接,省略则为普通标题。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| 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. |