跳至正文

Table of contents

紧凑的标题导航,支持悬停放大和滚动位置跟踪。

浏览器
Chrome 123+Firefox 121+Safari 17.5+
层级
2
压缩后大小
4.14 kB
强制布局预算
0 ms
axe 预算
0
支持 SSR
是
公开契约
测试环境

交互示例

Overview

A table of contents gives readers a quick map of a long page. Follow a heading to jump directly to its section, or keep reading and let the current position update as you scroll.

Installation

Import the client entry to enable scroll tracking and pointer interaction. The native button and heading links are already usable before the page finishes loading.

Configuration

Match every line and link to a unique heading ID. Choose a scroll container and a content boundary when the article lives inside another page.

Examples

Hover a line to see its heading in the contents list. Click the line to jump there. On a touch screen, open the list and select the heading you want to read.

Accessibility

The contents are ordinary navigation links. Use the keyboard to open the list, move through its headings, and dismiss it with Escape. Scrolling never moves keyboard focus.

Styling

Adjust line length, spacing and the panel width with CSS variables. The current section uses bolder text; the heading under the pointer gets a separate hover background.

Conclusion

The same component works beside a document or inside a reader. Its compact rail stays available while the article scrolls, without taking a separate content column.

import { TableOfContents } from "@gunkin/uiify/components/table-of-contents/client";

const sections = ["Overview", "Installation", "Configuration", "Examples", "Accessibility", "Styling", "Conclusion"];
export default function Example() {
  return (
    <div className="toc-reader" id="toc-reader" tabIndex={0} role="region" aria-label="Example article">
      <TableOfContents.Root
        className=""
        aria-label="Contents"
        scrollTarget="toc-reader"
        contentTarget="toc-article"
        offset={24}
      >
        <TableOfContents.Trigger target="toc-panel" aria-label="Contents">
          <TableOfContents.Rail>
            {sections.map((label, index) => (
              <TableOfContents.Indicator key={label} value={`toc-section-${index}`} />
            ))}
          </TableOfContents.Rail>
        </TableOfContents.Trigger>
        <TableOfContents.Content id="toc-panel" side="right" align="center">
          <TableOfContents.List>
            {sections.map((label, index) => (
              <TableOfContents.Link key={label} href={`#toc-section-${index}`}>
                {label}
              </TableOfContents.Link>
            ))}
          </TableOfContents.List>
        </TableOfContents.Content>
      </TableOfContents.Root>
      <article id="toc-article">
        {sections.map((label, index) => (
          <section id={`toc-section-${index}`} key={label}>
            <h2>{label}</h2>
            <p>Read this section, or choose another heading from the contents.</p>
          </section>
        ))}
      </article>
    </div>
  );
}
@layer components {
  [data-uiify-table-of-contents] {
    --uiify-toc-line-size: 12px;
    --uiify-toc-line-max-size: 48px;
    --uiify-toc-line-thickness: 2px;
    --uiify-toc-row-size: 20px;
    --uiify-toc-magnify-radius: 2.5;
    --uiify-toc-indent: 1rem;
    --uiify-toc-panel-size: 18rem;
    --uiify-toc-duration: 140ms;

    color: var(--foreground);
  }
  [data-uiify-table-of-contents] [data-part="trigger"] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-inline-size: 44px;
    min-block-size: 44px;
    padding: 0.75rem;
    border: 0;
    border-radius: var(--radius-2);
    background: transparent;
    color: inherit;
    font: inherit;
    cursor: pointer;
  }
  [data-uiify-table-of-contents] [data-part="rail"] {
    display: grid;
    grid-auto-rows: var(--uiify-toc-row-size);
    align-items: center;
    inline-size: var(--uiify-toc-line-max-size);
    max-block-size: calc(100dvb - 8rem);
    overflow: auto;
  }
  [data-uiify-table-of-contents] [data-part="indicator"] {
    display: block;

    --uiify-toc-distance: max(
      calc(var(--uiify-toc-index, 0) - var(--uiify-toc-pointer, -1000)),
      calc(var(--uiify-toc-pointer, -1000) - var(--uiify-toc-index, 0))
    );
    --uiify-toc-proximity: clamp(
      0,
      calc(1 - var(--uiify-toc-distance) / max(0.001, var(--uiify-toc-magnify-radius))),
      1
    );

    inline-size: calc(
      var(--uiify-toc-line-size) + (var(--uiify-toc-line-max-size) - var(--uiify-toc-line-size)) *
        var(--uiify-toc-proximity)
    );
    transition:
      inline-size var(--uiify-toc-duration) ease,
      opacity var(--uiify-toc-duration) ease;
    block-size: var(--uiify-toc-line-thickness);
    border-radius: var(--radius-full);
    background: var(--muted-foreground);
    opacity: 0.5;
  }
  [data-uiify-table-of-contents] [data-part="indicator"][data-selected] {
    background: var(--foreground);
    opacity: 1;
  }
  [data-uiify-table-of-contents] [data-part="indicator"][data-highlighted]:not([data-selected]) {
    background: var(--primary);
    opacity: 1;
  }
  [data-uiify-table-of-contents] [data-part="content"] {
    --uiify-anchor-offset: 0;

    inline-size: min(var(--uiify-toc-panel-size), calc(100dvi - 2rem));
    max-block-size: calc(100dvb - 2rem);
    overflow: auto;
    overscroll-behavior: contain;
    padding: 0.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-3);
    background: var(--popover);
    color: var(--popover-foreground);
  }
  [data-uiify-table-of-contents] [data-part="list"] {
    display: grid;
  }
  [data-uiify-table-of-contents] [data-part="link"] {
    display: block;
    padding-block: 0.625rem;
    padding-inline: calc(0.75rem + max(0, var(--uiify-toc-level, 2) - 2) * var(--uiify-toc-indent))
      0.75rem;
    border-radius: var(--radius-2);
    color: var(--muted-foreground);
    text-decoration: none;
    overflow-wrap: anywhere;
    font-size: 0.875rem;
    line-height: 1.4;
  }
  [data-uiify-table-of-contents] [data-part="link"][data-selected] {
    color: var(--foreground);
    font-weight: var(--weight-semibold);
  }
  [data-uiify-table-of-contents] [data-part="link"]:is(:hover, [data-highlighted]) {
    background: var(--accent);
    color: var(--accent-foreground);
  }
  [data-uiify-table-of-contents] :is([data-part="trigger"], [data-part="link"]):focus-visible {
    outline: 2px solid var(--ring);
    outline-offset: 2px;
  }

  @media (prefers-reduced-motion: reduce) {
    [data-uiify-table-of-contents] [data-part="indicator"] {
      transition: none;
    }
  }
}
.toc-reader { display: grid; grid-template-columns: 3rem minmax(0, 1fr); align-items: start; block-size: 28rem; overflow: auto; }
.toc-reader > nav { grid-column: 1; grid-row: 1; position: sticky; inset-block-start: 50%; translate: 0 -50%; --uiify-toc-line-size: 6px; --uiify-toc-line-max-size: 24px; }
.toc-reader > article { grid-column: 2; grid-row: 1; min-inline-size: 0; }
.toc-reader section { min-block-size: 18rem; padding: 1rem; scroll-margin-block-start: 24px; }
import { TableOfContents } from "@gunkin/uiify/components/table-of-contents/client";

const sections = ["Overview", "Installation", "Configuration", "Examples", "Accessibility", "Styling", "Conclusion"];
export default function Example() {
  return (
    <div
      className="grid grid-cols-[3rem_minmax(0,1fr)] [block-size:28rem] overflow-auto [&>article]:[min-inline-size:0] [&_section]:[min-block-size:18rem] [&_section]:p-4"
      id="toc-reader"
      tabIndex={0}
      role="region"
      aria-label="Example article"
    >
      <TableOfContents.Root
        className={
          "[--uiify-toc-line-size:12px] [--uiify-toc-line-max-size:48px] [--uiify-toc-line-thickness:2px] [--uiify-t" +
          "oc-row-size:20px] [--uiify-toc-magnify-radius:2.5] [--uiify-toc-indent:1rem] [--uiify-toc-panel-size:18re" +
          "m] [--uiify-toc-duration:140ms] text-foreground [&_[data-part=trigger]]:inline-flex [&_[data-part=trigger" +
          "]]:items-center [&_[data-part=trigger]]:justify-center [&_[data-part=trigger]]:[min-inline-size:44px] [&_" +
          "[data-part=trigger]]:[min-block-size:44px] [&_[data-part=trigger]]:[padding:0.75rem] [&_[data-part=trigge" +
          "r]]:border-0 [&_[data-part=trigger]]:border-current [&_[data-part=trigger]]:border-none [&_[data-part=tri" +
          "gger]]:[border-radius:var(--radius-2)] [&_[data-part=trigger]]:bg-transparent [&_[data-part=trigger]]:tex" +
          "t-inherit [&_[data-part=trigger]]:[font-style:inherit] [&_[data-part=trigger]]:[font-variant:inherit] [&_" +
          "[data-part=trigger]]:[font-weight:inherit] [&_[data-part=trigger]]:[font-stretch:inherit] [&_[data-part=t" +
          "rigger]]:text-[length:inherit] [&_[data-part=trigger]]:leading-[inherit] [&_[data-part=trigger]]:font-[fa" +
          "mily-name:inherit] [&_[data-part=trigger]]:cursor-pointer [&_[data-part=rail]]:grid [&_[data-part=rail]]:" +
          "[grid-auto-rows:var(--uiify-toc-row-size)] [&_[data-part=rail]]:items-center [&_[data-part=rail]]:[inline" +
          "-size:var(--uiify-toc-line-max-size)] [&_[data-part=rail]]:[max-block-size:calc(100dvb_-_8rem)] [&_[data-" +
          "part=rail]]:overflow-auto [&_[data-part=indicator]]:block [&_[data-part=indicator]]:[--uiify-toc-distance" +
          ":max(_calc(var(--uiify-toc-index,_0)_-_var(--uiify-toc-pointer,_-1000)),_calc(var(--uiify-toc-pointer,_-1" +
          "000)_-_var(--uiify-toc-index,_0))_)] [&_[data-part=indicator]]:[--uiify-toc-proximity:clamp(_0,_calc(1_-_" +
          "var(--uiify-toc-distance)_/_max(0.001,_var(--uiify-toc-magnify-radius))),_1_)] [&_[data-part=indicator]]:" +
          "[inline-size:calc(_var(--uiify-toc-line-size)_+_(var(--uiify-toc-line-max-size)_-_var(--uiify-toc-line-si" +
          "ze))_*_var(--uiify-toc-proximity)_)] [&_[data-part=indicator]]:[transition:inline-size_var(--uiify-toc-du" +
          "ration)_ease,_opacity_var(--uiify-toc-duration)_ease] [&_[data-part=indicator]]:[block-size:var(--uiify-t" +
          "oc-line-thickness)] [&_[data-part=indicator]]:rounded-[var(--radius-full)] [&_[data-part=indicator]]:bg-m" +
          "uted-foreground [&_[data-part=indicator]]:opacity-50 [&_[data-part=indicator][data-selected]]:bg-foregrou" +
          "nd [&_[data-part=indicator][data-selected]]:opacity-100 [&_[data-part=indicator][data-highlighted]:not([d" +
          "ata-selected])]:bg-primary [&_[data-part=indicator][data-highlighted]:not([data-selected])]:opacity-100 [" +
          "&_[data-part=content]]:[--uiify-anchor-offset:0] [&_[data-part=content]]:[inline-size:min(var(--uiify-toc" +
          "-panel-size),_calc(100dvi_-_2rem))] [&_[data-part=content]]:[max-block-size:calc(100dvb_-_2rem)] [&_[data" +
          "-part=content]]:overflow-auto [&_[data-part=content]]:overscroll-contain [&_[data-part=content]]:[padding" +
          ":0.5rem] [&_[data-part=content]]:[border-width:1px] [&_[data-part=content]]:border-solid [&_[data-part=co" +
          "ntent]]:border-border [&_[data-part=content]]:[border-radius:var(--radius-3)] [&_[data-part=content]]:bg-" +
          "popover [&_[data-part=content]]:text-popover-foreground [&_[data-part=list]]:grid [&_[data-part=link]]:bl" +
          "ock [&_[data-part=link]]:[padding-block:0.625rem] [&_[data-part=link]]:[padding-inline:calc(0.75rem_+_max" +
          "(0,_var(--uiify-toc-level,_2)_-_2)_*_var(--uiify-toc-indent))_0.75rem] [&_[data-part=link]]:[border-radiu" +
          "s:var(--radius-2)] [&_[data-part=link]]:text-muted-foreground [&_[data-part=link]]:no-underline [&_[data-" +
          "part=link]]:[overflow-wrap:anywhere] [&_[data-part=link]]:[font-size:0.875rem] [&_[data-part=link]]:[line" +
          "-height:1.4] [&_[data-part=link][data-selected]]:text-foreground [&_[data-part=link][data-selected]]:font" +
          "-[number:var(--weight-semibold)] [&_[data-part=link]:is(:hover,[data-highlighted])]:bg-accent [&_[data-pa" +
          "rt=link]:is(:hover,[data-highlighted])]:text-accent-foreground [&_:is([data-part=trigger],[data-part=link" +
          "]):focus-visible]:[outline:2px_solid_var(--ring)] [&_:is([data-part=trigger],[data-part=link]):focus-visi" +
          "ble]:[outline-offset:2px] motion-reduce:[&_[data-part=indicator]]:[transition:none]"
        }
        aria-label="Contents"
        scrollTarget="toc-reader"
        contentTarget="toc-article"
        offset={24}
      >
        <TableOfContents.Trigger target="toc-panel" aria-label="Contents">
          <TableOfContents.Rail>
            {sections.map((label, index) => (
              <TableOfContents.Indicator key={label} value={`toc-section-${index}`} />
            ))}
          </TableOfContents.Rail>
        </TableOfContents.Trigger>
        <TableOfContents.Content id="toc-panel" side="right" align="center">
          <TableOfContents.List>
            {sections.map((label, index) => (
              <TableOfContents.Link key={label} href={`#toc-section-${index}`}>
                {label}
              </TableOfContents.Link>
            ))}
          </TableOfContents.List>
        </TableOfContents.Content>
      </TableOfContents.Root>
      <article id="toc-article">
        {sections.map((label, index) => (
          <section id={`toc-section-${index}`} key={label}>
            <h2>{label}</h2>
            <p>Read this section, or choose another heading from the contents.</p>
          </section>
        ))}
      </article>
    </div>
  );
}
/* app.css */

@layer reset, ui, app, tokens, base, demo, components, utilities, theme;

@import "@gunkin/uiify/styles/reset";

@import "@gunkin/uiify/components/behavior.css";

@import "@gunkin/uiify/styles/tokens";

@import "@gunkin/uiify/styles/tailwind";

@import "tailwindcss/utilities.css";

@theme { --spacing: 0.25rem; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; }

@source inline('[--uiify-toc-line-size:12px] [--uiify-toc-line-max-size:48px] [--uiify-toc-line-thickness:2px] [--uiify-toc-row-size:20px] [--uiify-toc-magnify-radius:2.5] [--uiify-toc-indent:1rem] [--uiify-toc-panel-size:18rem] [--uiify-toc-duration:140ms] text-foreground [&_[data-part=trigger]]:inline-flex [&_[data-part=trigger]]:items-center [&_[data-part=trigger]]:justify-center [&_[data-part=trigger]]:[min-inline-size:44px] [&_[data-part=trigger]]:[min-block-size:44px] [&_[data-part=trigger]]:[padding:0.75rem] [&_[data-part=trigger]]:border-0 [&_[data-part=trigger]]:border-current [&_[data-part=trigger]]:border-none [&_[data-part=trigger]]:[border-radius:var(--radius-2)] [&_[data-part=trigger]]:bg-transparent [&_[data-part=trigger]]:text-inherit [&_[data-part=trigger]]:[font-style:inherit] [&_[data-part=trigger]]:[font-variant:inherit] [&_[data-part=trigger]]:[font-weight:inherit] [&_[data-part=trigger]]:[font-stretch:inherit] [&_[data-part=trigger]]:text-[length:inherit] [&_[data-part=trigger]]:leading-[inherit] [&_[data-part=trigger]]:font-[family-name:inherit] [&_[data-part=trigger]]:cursor-pointer [&_[data-part=rail]]:grid [&_[data-part=rail]]:[grid-auto-rows:var(--uiify-toc-row-size)] [&_[data-part=rail]]:items-center [&_[data-part=rail]]:[inline-size:var(--uiify-toc-line-max-size)] [&_[data-part=rail]]:[max-block-size:calc(100dvb_-_8rem)] [&_[data-part=rail]]:overflow-auto [&_[data-part=indicator]]:block [&_[data-part=indicator]]:[--uiify-toc-distance:max(_calc(var(--uiify-toc-index,_0)_-_var(--uiify-toc-pointer,_-1000)),_calc(var(--uiify-toc-pointer,_-1000)_-_var(--uiify-toc-index,_0))_)] [&_[data-part=indicator]]:[--uiify-toc-proximity:clamp(_0,_calc(1_-_var(--uiify-toc-distance)_/_max(0.001,_var(--uiify-toc-magnify-radius))),_1_)] [&_[data-part=indicator]]:[inline-size:calc(_var(--uiify-toc-line-size)_+_(var(--uiify-toc-line-max-size)_-_var(--uiify-toc-line-size))_*_var(--uiify-toc-proximity)_)] [&_[data-part=indicator]]:[transition:inline-size_var(--uiify-toc-duration)_ease,_opacity_var(--uiify-toc-duration)_ease] [&_[data-part=indicator]]:[block-size:var(--uiify-toc-line-thickness)] [&_[data-part=indicator]]:rounded-[var(--radius-full)] [&_[data-part=indicator]]:bg-muted-foreground [&_[data-part=indicator]]:opacity-50 [&_[data-part=indicator][data-selected]]:bg-foreground [&_[data-part=indicator][data-selected]]:opacity-100 [&_[data-part=indicator][data-highlighted]:not([data-selected])]:bg-primary [&_[data-part=indicator][data-highlighted]:not([data-selected])]:opacity-100 [&_[data-part=content]]:[--uiify-anchor-offset:0] [&_[data-part=content]]:[inline-size:min(var(--uiify-toc-panel-size),_calc(100dvi_-_2rem))] [&_[data-part=content]]:[max-block-size:calc(100dvb_-_2rem)] [&_[data-part=content]]:overflow-auto [&_[data-part=content]]:overscroll-contain [&_[data-part=content]]:[padding:0.5rem] [&_[data-part=content]]:[border-width:1px] [&_[data-part=content]]:border-solid [&_[data-part=content]]:border-border [&_[data-part=content]]:[border-radius:var(--radius-3)] [&_[data-part=content]]:bg-popover [&_[data-part=content]]:text-popover-foreground [&_[data-part=list]]:grid [&_[data-part=link]]:block [&_[data-part=link]]:[padding-block:0.625rem] [&_[data-part=link]]:[padding-inline:calc(0.75rem_+_max(0,_var(--uiify-toc-level,_2)_-_2)_*_var(--uiify-toc-indent))_0.75rem] [&_[data-part=link]]:[border-radius:var(--radius-2)] [&_[data-part=link]]:text-muted-foreground [&_[data-part=link]]:no-underline [&_[data-part=link]]:[overflow-wrap:anywhere] [&_[data-part=link]]:[font-size:0.875rem] [&_[data-part=link]]:[line-height:1.4] [&_[data-part=link][data-selected]]:text-foreground [&_[data-part=link][data-selected]]:font-[number:var(--weight-semibold)] [&_[data-part=link]:is(:hover,[data-highlighted])]:bg-accent [&_[data-part=link]:is(:hover,[data-highlighted])]:text-accent-foreground [&_:is([data-part=trigger],[data-part=link]):focus-visible]:[outline:2px_solid_var(--ring)] [&_:is([data-part=trigger],[data-part=link]):focus-visible]:[outline-offset:2px] motion-reduce:[&_[data-part=indicator]]:[transition:none] grid grid-cols-[3rem_minmax(0,1fr)] [block-size:28rem] overflow-auto [&>article]:[min-inline-size:0] [&_section]:[min-block-size:18rem] [&_section]:p-4');

@layer base { :root { --uiify-control-text: var(--text-sm); } @media (pointer: coarse) { :root { --uiify-control-text: var(--text-base); } } }

/* Authored animation and registered properties used by the Tailwind skin.
 * Visual rules belong in utility recipes; only at-rule definitions live here. */
@layer components {
  @keyframes uiify-presence-in {
    from {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@layer components {
  @keyframes uiify-presence-out {
    to {
      opacity: 0;
      scale: var(--presence-scale);
    }
  }
}

@property --uiify-spinner-tail {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@property --uiify-spinner-length {
  syntax: "<angle>";
  inherits: false;
  initial-value: 36deg;
}

@layer components {
  @keyframes uiify-spinner-rotate {
    to {
      transform: rotate(360deg);
    }
  }
}

@layer components {
  @keyframes uiify-spinner-orbit {
    0% {
      --uiify-spinner-tail: 0deg;
      --uiify-spinner-length: 36deg;
    }

    50% {
      --uiify-spinner-tail: 90deg;
      --uiify-spinner-length: 324deg;
    }

    100% {
      --uiify-spinner-tail: 360deg;
      --uiify-spinner-length: 36deg;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-pulse {
    50% {
      opacity: 0.45;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-shimmer {
    to {
      background-position: -200% 0;
    }
  }
}

@layer components {
  @keyframes uiify-skeleton-reveal {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
}

@property --candle-body-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-body-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-slot {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 100%;
}

@property --candle-wick-height {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-wick-top {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --candle-x {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 0%;
}

@property --candle-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@property --line-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-prev-y {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 50%;
}

@property --line-count {
  syntax: "<integer>";
  inherits: false;
  initial-value: 1;
}

@property --line-scale {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

@layer components {
  @keyframes uiify-otp-input-caret-blink {
    50% {
      opacity: 0;
    }
  }
}

API

TableOfContents.Root (/client)

属性类型默认值说明
scrollSpybooleantrue水合后,按文档顺序跟踪同页目标。
scrollTargetstring—可滚动元素的ID。省略时跟踪所属window;ID不存在时不会回退。
contentTargetstring—内容元素的ID,用于限制目标查找范围并确定文章末尾。
offsetnumber0从滚动视口起点向下偏移的像素数,用于选择当前标题。
valuestring | null—受控的当前目标ID。null清除选择。
defaultValuestring | nullnull初始的当前目标ID。
onValueChange(value: string | null) => void—报告滚动位置跟踪变化,不移动焦点。
openboolean—受控的弹出层状态。
defaultOpenbooleanfalse水合后应用的初始打开状态。
onOpenChange(open: boolean) => void—报告悬停、激活和原生关闭请求。
openOnHoverbooleantrue鼠标悬停时打开;触摸使用原生按钮。
openDelaynumber100鼠标悬停打开延迟,单位为毫秒。
closeDelaynumber200在触发器与列表之间移动的宽限时间。
renderReactElement | ((props, state) => ReactElement)—替换渲染元素,同时保留行为和无障碍连接关系。

TableOfContents.Trigger

属性类型默认值说明
target*string—唯一的Content ID,通过原生popovertarget关联。
aria-labelstring—触发器仅含装饰性轨道时,必须提供的无障碍名称。

TableOfContents.Indicator

属性类型默认值说明
value*string—与Link片段匹配的目标ID;每个区块一个指示器。
level1 | 2 | 3 | 4 | 5 | 62通过--uiify-toc-level提供的标题层级。
selectedbooleanfalse服务端入口的选择状态;客户端从Root.value派生选择状态。
renderReactElement | ((props, state) => ReactElement)—替换渲染元素,同时保留行为和无障碍连接关系。

TableOfContents.Content

属性类型默认值说明
id*string—与Trigger.target匹配的唯一弹出层ID。
side"top" | "right" | "bottom" | "left""left"原生CSS锚定侧边;必要时翻转布局。
align"start" | "center" | "end""start"在触发器旁的对齐方式。
renderReactElement | ((props, state) => ReactElement)—替换渲染元素,同时保留行为和无障碍连接关系。

TableOfContents.Link

TableOfContents.Rail / List

属性类型默认值说明
renderReactElement | ((props, state) => ReactElement)—替换渲染元素,同时保留行为和无障碍连接关系。

键盘

按键操作
Enter / Space通过触发器切换原生目录弹出层
Tab / Shift+Tab在普通标题链接之间移动
Escape关闭弹出层,将焦点从列表返回触发器

无障碍

为导航地标及其纯图标触发器提供标签。服务端入口支持水合前的原生弹出层激活、关闭和片段链接;/client入口添加悬停、点击固定、放大和滚动位置跟踪。当前链接暴露aria-current=location,装饰性指示器对辅助技术隐藏。滚动位置跟踪不会移动焦点或播报滚动。启用减少动态效果时,可选皮肤会禁用线条过渡。

样式钩子

data-uiify-table-of-contentsdata-partdata-selecteddata-highlighteddata-statedata-sidedata-aligndata-positioning--uiify-toc-line-size--uiify-toc-line-max-size--uiify-toc-line-thickness--uiify-toc-row-size--uiify-toc-magnify-radius--uiify-toc-indent--uiify-toc-panel-size--uiify-toc-duration--uiify-toc-level--uiify-toc-index--uiify-toc-pointer