Table of Contents 目录
@lark-ui/core
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”(目录)。

属性类型默认值
childrenReactNode-
classNamestring-
idstring-
langstring-
titlestring-

TableOfContents.Title

可选的大写标题,显示在列表上方(渲染为 <p>)。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。

TableOfContents.List

带左侧边框导轨的列表容器。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。

TableOfContents.Item

单个导航链接。为当前章节设置 active。 使用 render 属性可把锚点替换为路由链接或按钮。

属性类型默认值说明
activeboolean-该条目是否表示当前激活的章节。
renderReact.ReactElement-渲染为链接的自定义元素。用于接入框架路由(例如 Next.js 的 `<Link>`、React Router 的 `<NavLink>`)。该元素会接收包括 `href`、`className` 和 `children` 在内的所有锚点属性。

TableOfContents.Group

将条目归入带标签的分区,子项以缩进形式展示。传入 href 可让标签变成可点击链接,省略则使用普通标题。

属性类型默认值说明
label*string-显示在该分组条目上方的标签。
hrefstring-分组标签链接到的 URL。提供后,标签会渲染为带条目样式的可点击链接。
activeboolean-该分组的标签是否表示当前激活的章节。仅在提供 `href` 时生效。