import { TableOfContents } from "@lark-ui/core";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
export function TableOfContentsBasicDemo() {
return (
<DemoWrapper>
<TableOfContents>
<TableOfContents.Title>本页内容</TableOfContents.Title>
<TableOfContents.List>
{headings.map((heading) => (
<TableOfContents.Item
key={heading.text}
active={heading.text === "用法"}
className="cursor-pointer"
>
{heading.text}
</TableOfContents.Item>
))}
</TableOfContents.List>
</TableOfContents>
</DemoWrapper>
);
}安装
统一导出
import { TableOfContents } from "@lark-ui/core";按需导出
import { TableOfContents } from "@lark-ui/core/components/table-of-contents";用法
import { TableOfContents } from "@lark-ui/core";
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 操作,
用于在被点击的章节上锁定高亮,直到平滑滚动结束(这样跳转后较短的章节仍能保持高亮)。
哈希深链接(#usage)会被自动处理。该 hook 对 SSR 安全——所有 DOM 访问都发生在副作用中。
import { TableOfContents, useTableOfContentsActiveId } from "@lark-ui/core";
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):
概览
用于 概览 分区的可滚动占位文本。 用于 概览 分区的可滚动占位文本。 用于 概览 分区的可滚动占位文本。 用于 概览 分区的可滚动占位文本。 用于 概览 分区的可滚动占位文本。 用于 概览 分区的可滚动占位文本。
安装
用于 安装 分区的可滚动占位文本。 用于 安装 分区的可滚动占位文本。 用于 安装 分区的可滚动占位文本。 用于 安装 分区的可滚动占位文本。 用于 安装 分区的可滚动占位文本。 用于 安装 分区的可滚动占位文本。
用法
用于 用法 分区的可滚动占位文本。 用于 用法 分区的可滚动占位文本。 用于 用法 分区的可滚动占位文本。 用于 用法 分区的可滚动占位文本。 用于 用法 分区的可滚动占位文本。 用于 用法 分区的可滚动占位文本。
API
用于 API 分区的可滚动占位文本。 用于 API 分区的可滚动占位文本。 用于 API 分区的可滚动占位文本。 用于 API 分区的可滚动占位文本。 用于 API 分区的可滚动占位文本。 用于 API 分区的可滚动占位文本。
import { useState } from "react";
import { TableOfContents, useTableOfContentsActiveId } from "@lark-ui/core";
/**
* 通过 `useTableOfContentsActiveId` 实现实时滚动跟踪,并通过 `root` 选项将作用域
* 限定到自定义滚动容器。滚动内容——当前条目会跟随高亮;点击某个条目即可跳转。
*/
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>本页内容</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-lark-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-lark-subtle">
{Array.from(
{ length: 6 },
() => `用于 ${section.title} 分区的可滚动占位文本。 `,
).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 "@lark-ui/core";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
export function TableOfContentsInteractiveDemo() {
const [active, setActive] = useState("简介");
return (
<DemoWrapper>
<TableOfContents>
<TableOfContents.Title>本页内容</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 "@lark-ui/core";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
export function TableOfContentsNoActiveDemo() {
return (
<DemoWrapper>
<TableOfContents>
<TableOfContents.Title>本页内容</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」),
或省略该属性以使用普通的非交互式标题(如下方的「入门」)。
import { TableOfContents } from "@lark-ui/core";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
/** 展示两种分组模式:可点击的分组标签(带 `href`)和纯文本标题标签(不带 `href`)。 */
export function TableOfContentsGroupDemo() {
return (
<DemoWrapper>
<TableOfContents>
<TableOfContents.Title>本页内容</TableOfContents.Title>
<TableOfContents.List>
<TableOfContents.Item active className="cursor-pointer">
概览
</TableOfContents.Item>
<TableOfContents.Group label="示例" href="#examples-demo">
<TableOfContents.Item className="cursor-pointer">
基础示例
</TableOfContents.Item>
<TableOfContents.Item className="cursor-pointer">
高级示例
</TableOfContents.Item>
</TableOfContents.Group>
<TableOfContents.Group label="快速开始">
<TableOfContents.Item className="cursor-pointer">
安装
</TableOfContents.Item>
<TableOfContents.Item className="cursor-pointer">
配置
</TableOfContents.Item>
</TableOfContents.Group>
<TableOfContents.Group label="API" href="#api-demo">
<TableOfContents.Item className="cursor-pointer">
属性
</TableOfContents.Item>
<TableOfContents.Item className="cursor-pointer">
事件
</TableOfContents.Item>
</TableOfContents.Group>
</TableOfContents.List>
</TableOfContents>
</DemoWrapper>
);
}无标题
标题子组件是可选的——如果不需要标题,可以直接使用 TableOfContents.List。
import { TableOfContents } from "@lark-ui/core";
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 === "简介"}
className="cursor-pointer"
>
{heading.text}
</TableOfContents.Item>
))}
</TableOfContents.List>
</TableOfContents>
</DemoWrapper>
);
}自定义元素
使用 render 属性可以把默认的锚点替换为按钮、路由链接或任意元素。
import { useState } from "react";
import { TableOfContents } from "@lark-ui/core";
function DemoWrapper({ children }: { children: React.ReactNode }) {
return <div className="min-w-48">{children}</div>;
}
/** 演示使用 `render` 属性配合自定义链接组件。 */
export function TableOfContentsRenderPropDemo() {
const [clicked, setClicked] = useState<string | null>(null);
return (
<DemoWrapper>
<div className="space-y-3">
<TableOfContents>
<TableOfContents.List>
{["简介", "安装", "用法"].map((text) => (
<TableOfContents.Item
key={text}
render={<button type="button" />}
onClick={() => setClicked(text)}
active={text === "简介"}
>
{text}
</TableOfContents.Item>
))}
</TableOfContents.List>
</TableOfContents>
{clicked && (
<p className="text-xs text-lark-subtle">已点击:{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”(目录)。
| 属性 | 类型 | 默认值 |
|---|---|---|
| children | ReactNode | - |
| className | string | - |
| id | string | - |
| lang | string | - |
| title | string | - |
TableOfContents.Title
可选的大写标题,显示在列表上方(渲染为 <p>)。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
TableOfContents.List
带左侧边框导轨的列表容器。
| 属性 | 类型 | 默认值 |
|---|
该组件没有专有属性,支持标准 HTML 属性。
TableOfContents.Item
单个导航链接。为当前章节设置 active。
使用 render 属性可把锚点替换为路由链接或按钮。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| active | boolean | - | 该条目是否表示当前激活的章节。 |
| render | React.ReactElement | - | 渲染为链接的自定义元素。用于接入框架路由(例如 Next.js 的 `<Link>`、React Router 的 `<NavLink>`)。该元素会接收包括 `href`、`className` 和 `children` 在内的所有锚点属性。 |
TableOfContents.Group
将条目归入带标签的分区,子项以缩进形式展示。传入 href 可让标签变成可点击链接,省略则使用普通标题。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| label* | string | - | 显示在该分组条目上方的标签。 |
| href | string | - | 分组标签链接到的 URL。提供后,标签会渲染为带条目样式的可点击链接。 |
| active | boolean | - | 该分组的标签是否表示当前激活的章节。仅在提供 `href` 时生效。 |