К содержимому
uiify

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 не найден, перехода к окну нет.
contentTargetstring—Идентификатор элемента содержимого, ограничивающий поиск целей и определяющий конец статьи.
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—Уникальный ID Content, связанный через нативный popovertarget.
aria-labelstring—Доступное название, обязательное, если триггер содержит только декоративную полосу.

TableOfContents.Indicator

СвойствоТипПо умолчаниюОписание
value*string—ID цели, совпадающий с фрагментом Link; один индикатор на раздел.
level1 | 2 | 3 | 4 | 5 | 62Глубина заголовка, доступная через --uiify-toc-level.
selectedbooleanfalseВыбор для серверной точки входа; клиент получает выбор из Root.value.
renderReactElement | ((props, state) => ReactElement)—Заменяет отображаемый элемент, сохраняя поведение и связи доступности.

TableOfContents.Content

СвойствоТипПо умолчаниюОписание
id*string—Уникальный ID всплывающего блока, совпадающий с Trigger.target.
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