# Table of Contents

URL: https://ark-ui.com/docs/components/toc
LLM: https://ark-ui.com/llms.txt/components/toc

A navigation component that tracks and highlights the active section as users scroll through document content.

---



## Anatomy



```tsx
<Toc.Root items={items} scrollEl={() => scrollContainer}>
  <Toc.Content />
  <Toc.Nav>
    <Toc.Title />
    <Toc.List>
      <Toc.Indicator />
      <Toc.Item item={item}>
        <Toc.Link />
      </Toc.Item>
    </Toc.List>
  </Toc.Nav>
</Toc.Root>
```

## Examples

### Basic

Pass headings to `items`, and point `scrollEl` at the scrollable container so the TOC knows what to track.

```tsx
import { Toc } from '@ark-ui/react/toc'
import { useRef } from 'react'
import styles from 'styles/toc.module.css'

const items = [
  { value: '01-introduction', depth: 2, label: 'Introduction', lines: 12 },
  { value: '01-getting-started', depth: 2, label: 'Getting Started', lines: 10 },
  { value: '01-installation', depth: 2, label: 'Installation', lines: 8 },
  { value: '01-usage', depth: 2, label: 'Usage', lines: 14 },
  { value: '01-conclusion', depth: 2, label: 'Conclusion', lines: 10 },
]

export const Basic = () => {
  const contentRef = useRef<HTMLElement | null>(null)

  return (
    <Toc.Root className={styles.Root} items={items} scrollEl={() => contentRef.current}>
      <Toc.Content className={styles.Content} ref={contentRef}>
        {items.map((item) => (
          <section key={item.value} className={styles.Section}>
            <h2 id={item.value} className={styles.Heading} data-depth={item.depth}>
              {item.label}
            </h2>
            <div className={styles.DummyText}>
              {Array.from({ length: item.lines }).map((_, i) => (
                <div key={i} className={styles.DummyLine} />
              ))}
            </div>
          </section>
        ))}
      </Toc.Content>

      <Toc.Nav className={styles.Nav}>
        <Toc.Title className={styles.Title}>On this page</Toc.Title>
        <Toc.List className={styles.List}>
          {items.map((item) => (
            <Toc.Item className={styles.Item} key={item.value} item={item}>
              <Toc.Link className={styles.Link} href={`#${item.value}`}>
                {item.label}
              </Toc.Link>
            </Toc.Item>
          ))}
        </Toc.List>
      </Toc.Nav>
    </Toc.Root>
  )
}
```

### Nested Headings

Read `depth` in your own markup to indent sub-headings. Nothing is indented for you.

```tsx
import { Toc } from '@ark-ui/react/toc'
import { useRef } from 'react'
import styles from 'styles/toc.module.css'

const items = [
  { value: '02-importance', depth: 2, label: 'Importance', lines: 10 },
  { value: '02-integrations', depth: 2, label: 'Integrations', lines: 12 },
  { value: '02-free-blocks', depth: 3, label: 'Free Blocks', lines: 8 },
  { value: '02-configuration', depth: 3, label: 'Configuration', lines: 14 },
  { value: '02-api-reference', depth: 2, label: 'API Reference', lines: 10 },
  { value: '02-hooks', depth: 3, label: 'Hooks', lines: 8 },
  { value: '02-components', depth: 3, label: 'Components', lines: 12 },
  { value: '02-examples', depth: 2, label: 'Examples', lines: 10 },
]

export const Nested = () => {
  const contentRef = useRef<HTMLElement | null>(null)

  return (
    <Toc.Root className={`${styles.Root}`} items={items} scrollEl={() => contentRef.current}>
      <Toc.Content className={styles.Content} ref={contentRef}>
        {items.map((item) => (
          <section key={item.value}>
            <div id={item.value} className={styles.Heading} data-depth={item.depth}>
              {item.label}
            </div>
            <div className={styles.DummyText}>
              {Array.from({ length: item.lines }).map((_, i) => (
                <div key={i} className={styles.DummyLine} />
              ))}
            </div>
          </section>
        ))}
      </Toc.Content>

      <Toc.Nav className={styles.Nav}>
        <Toc.Title className={styles.Title}>On this page</Toc.Title>
        <Toc.List className={styles.List}>
          {items.map((item) => (
            <Toc.Item className={item.depth > 2 ? styles.ItemNested : styles.Item} key={item.value} item={item}>
              <Toc.Link className={styles.Link} href={`#${item.value}`}>
                {item.label}
              </Toc.Link>
            </Toc.Item>
          ))}
        </Toc.List>
      </Toc.Nav>
    </Toc.Root>
  )
}
```

### Root Provider

Use `useToc` with `Toc.RootProvider` to reach `activeIds` from outside the tree, so other parts of your UI can follow
the reading position.

```tsx
import { Toc, useToc } from '@ark-ui/react/toc'
import { useRef } from 'react'
import styles from 'styles/toc.module.css'

const items = [
  { value: '03-introduction', depth: 2, label: 'Introduction', lines: 12 },
  { value: '03-getting-started', depth: 2, label: 'Getting Started', lines: 10 },
  { value: '03-installation', depth: 2, label: 'Installation', lines: 8 },
  { value: '03-usage', depth: 2, label: 'Usage', lines: 14 },
  { value: '03-conclusion', depth: 2, label: 'Conclusion', lines: 10 },
]

export const RootProvider = () => {
  const contentRef = useRef<HTMLElement | null>(null)

  const toc = useToc({
    items,
    scrollEl: () => contentRef.current,
  })

  return (
    <div className="vstack" style={{ width: '100%' }}>
      <output>activeIds: {JSON.stringify(toc.activeIds)}</output>
      <Toc.RootProvider className={styles.Root} value={toc}>
        <Toc.Content className={styles.Content} ref={contentRef}>
          {items.map((item) => (
            <section key={item.value} className={styles.Section}>
              <h2 id={item.value} className={styles.Heading} data-depth={item.depth}>
                {item.label}
              </h2>
              <div className={styles.DummyText}>
                {Array.from({ length: item.lines }).map((_, i) => (
                  <div key={i} className={styles.DummyLine} />
                ))}
              </div>
            </section>
          ))}
        </Toc.Content>

        <Toc.Nav className={styles.Nav}>
          <Toc.Title className={styles.Title}>On this page</Toc.Title>
          <Toc.List className={styles.List}>
            {items.map((item) => (
              <Toc.Item className={styles.Item} key={item.value} item={item}>
                <Toc.Link className={styles.Link} href={`#${item.value}`}>
                  {item.label}
                </Toc.Link>
              </Toc.Item>
            ))}
          </Toc.List>
        </Toc.Nav>
      </Toc.RootProvider>
    </div>
  )
}
```

### With Collapsible

Wrap `Toc.Nav` in a `Collapsible` to let users hide the navigation. `Toc.Context` exposes `activeItems`, here driving a
progress ring.

```tsx
import { Collapsible } from '@ark-ui/react/collapsible'
import { Toc } from '@ark-ui/react/toc'
import { ChevronRightIcon } from 'lucide-react'
import CollapsibleStyles from 'styles/collapsible.module.css'
import styles from 'styles/toc.module.css'
import { useRef } from 'react'

const items = [
  { value: '04-overview', depth: 2, label: 'Overview', lines: 8 },
  { value: '04-prerequisites', depth: 2, label: 'Prerequisites', lines: 5 },
  { value: '04-quick-start', depth: 2, label: 'Quick Start', lines: 20 },
  { value: '04-commands', depth: 2, label: 'Core Commands', lines: 15 },
  { value: '04-troubleshooting', depth: 2, label: 'Troubleshooting', lines: 12 },
]

export const WithCollapsible = () => {
  const contentRef = useRef<HTMLElement | null>(null)

  return (
    <Toc.Root className={styles.Root} data-stacked items={items} scrollEl={() => contentRef.current}>
      <Collapsible.Root className={CollapsibleStyles.Root} style={{ width: '100%' }}>
        <Toc.Context>
          {({ activeItems }) => {
            const activeIndex = items.findIndex((i) => i.value === activeItems[0]?.value)
            const activeLabel = items[activeIndex]?.label ?? 'On this page'

            return (
              <Collapsible.Trigger className={CollapsibleStyles.Trigger}>
                <span className={styles.TriggerContent}>
                  <Ring index={activeIndex} total={items.length} />
                  <span key={activeLabel} className={styles.TriggerLabel}>
                    {activeLabel}
                  </span>
                </span>
                <Collapsible.Indicator className={CollapsibleStyles.Indicator}>
                  <ChevronRightIcon />
                </Collapsible.Indicator>
              </Collapsible.Trigger>
            )
          }}
        </Toc.Context>
        <Collapsible.Content className={CollapsibleStyles.Content}>
          <Toc.List className={styles.List}>
            {items.map((item, index) => (
              <Toc.Item className={styles.Item} key={item.value} item={item}>
                <Toc.Link className={styles.LinkNumbered} href={`#${item.value}`}>
                  <span className={styles.Number}>{String(index + 1).padStart(2, '0')}</span>
                  {item.label}
                </Toc.Link>
              </Toc.Item>
            ))}
          </Toc.List>
        </Collapsible.Content>
      </Collapsible.Root>

      <Toc.Content className={styles.Content} ref={contentRef}>
        {items.map((item) => (
          <section key={item.value}>
            <h2 id={item.value} className={styles.Heading} data-depth={item.depth}>
              {item.label}
            </h2>
            <div className={styles.DummyText}>
              {Array.from({ length: item.lines }).map((_, i) => (
                <div key={i} className={styles.DummyLine} />
              ))}
            </div>
          </section>
        ))}
      </Toc.Content>
    </Toc.Root>
  )
}

const Ring = ({ index, total }: { index: number; total: number }) => {
  const progress = index >= 0 ? (index + 1) / total : 0
  return (
    <svg width="28" height="28" viewBox="0 0 36 36" aria-hidden="true" className={styles.ProgressRing}>
      <circle cx="18" cy="18" r="14" fill="none" stroke="currentColor" strokeOpacity="0.2" strokeWidth="2.5" />
      <circle
        cx="18"
        cy="18"
        r="14"
        fill="none"
        pathLength="100"
        stroke="var(--demo-coral-solid)"
        strokeWidth="2.5"
        strokeDasharray={`${progress * 100} 100`}
        strokeLinecap="round"
        transform="rotate(-90 18 18)"
        style={{ transition: 'stroke-dasharray 0.4s ease-out' }}
      />
      <text
        key={index}
        x="18"
        y="18"
        textAnchor="middle"
        dominantBaseline="central"
        fontSize="10"
        fontWeight="600"
        fill="currentColor"
        className={styles.ProgressIndexText}
      >
        {index >= 0 ? index + 1 : '—'}
      </text>
    </svg>
  )
}
```

### With Hover

Expand the navigation on `onMouseEnter` and collapse it on `onMouseLeave`, with a pin toggle to keep it open.

> **Note:** Hover does not exist on touch screens. Pair this with a pin button or a disclosure control so the navigation
> stays reachable on mobile.

```tsx
import { Swap } from '@ark-ui/react/swap'
import { Toc } from '@ark-ui/react/toc'
import { useRef, useState } from 'react'
import styles from 'styles/toc.module.css'

const items = [
  { value: '05-analytics-dashboard', depth: 2, label: 'Real-time Analytics', lines: 55 },
  { value: '05-cloud-storage', depth: 2, label: 'S3 Cloud Storage', lines: 14 },
  { value: '05-automation-tools', depth: 2, label: 'Workflow Automation', lines: 32 },
  { value: '05-crm-integration', depth: 2, label: 'Salesforce Sync', lines: 45 },
  { value: '05-report-generator', depth: 2, label: 'Custom PDF Reports', lines: 20 },
]

export const WithHover = () => {
  const [hovered, setHovered] = useState(false)
  const contentRef = useRef<HTMLElement | null>(null)

  return (
    <Toc.Root className={`${styles.Root} ${styles.HoverRoot}`} items={items} scrollEl={() => contentRef.current}>
      <Toc.Content className={styles.Content} ref={contentRef}>
        {items.map((item) => (
          <section key={item.value}>
            <h2 id={item.value} className={styles.Heading} data-depth={item.depth}>
              {item.label}
            </h2>
            <div className={styles.DummyText}>
              {Array.from({ length: item.lines }).map((_, i) => (
                <div key={i} className={styles.DummyLine} />
              ))}
            </div>
          </section>
        ))}
      </Toc.Content>
      <Toc.Nav
        className={styles.NavHover}
        data-expanded={hovered || undefined}
        onMouseEnter={() => setHovered(true)}
        onMouseLeave={() => setHovered(false)}
      >
        <Swap.Root className={styles.HoverSwap} swap={hovered}>
          <Swap.Indicator type="off" className={styles.HoverSkeleton}>
            {items.map((item) => (
              <Toc.Item item={item} key={item.value} className={styles.SkeletonBar} />
            ))}
          </Swap.Indicator>
          <Swap.Indicator type="on" className={styles.HoverList}>
            {items.map((item) => (
              <Toc.Item key={item.value} item={item} className={styles.Item}>
                <Toc.Link className={styles.HoverLink} href={`#${item.value}`}>
                  {item.label}
                </Toc.Link>
              </Toc.Item>
            ))}
          </Swap.Indicator>
        </Swap.Root>
      </Toc.Nav>
    </Toc.Root>
  )
}
```

### With Indicator

Add `Toc.Indicator` inside `Toc.List` for a marker that slides to the active item.

```tsx
import { Toc } from '@ark-ui/react/toc'
import styles from 'styles/toc.module.css'
import { useRef } from 'react'

const items = [
  { value: '06-step-validation', depth: 2, label: 'Validation Pending', lines: 5 },
  { value: '06-upload-progress', depth: 2, label: 'Asset Uploading', lines: 90 },
  { value: '06-deployment-sync', depth: 2, label: 'Server Sync Active', lines: 12 },
  { value: '06-build-pipeline', depth: 2, label: 'CI/CD Running', lines: 105 },
  { value: '06-database-health', depth: 2, label: 'DB Connection Stable', lines: 3 },
]

export const WithIndicator = () => {
  const contentRef = useRef<HTMLElement | null>(null)

  return (
    <Toc.Root className={styles.Root} items={items} scrollEl={() => contentRef.current}>
      <Toc.Content className={styles.Content} ref={contentRef}>
        {items.map((item) => (
          <section key={item.value}>
            <h2 id={item.value} className={styles.Heading} data-depth={item.depth}>
              {item.label}
            </h2>
            <div className={styles.DummyText}>
              {Array.from({ length: item.lines }).map((_, i) => (
                <div key={i} className={styles.DummyLine} />
              ))}
            </div>
          </section>
        ))}
      </Toc.Content>

      <Toc.Nav className={styles.Nav}>
        <Toc.Title className={styles.Title}>On this page</Toc.Title>
        <Toc.List className={styles.List}>
          <Toc.Indicator className={styles.Indicator} />
          {items.map((item) => (
            <Toc.Item className={styles.Item} key={item.value} item={item}>
              <Toc.Link className={styles.Link} href={`#${item.value}`}>
                {item.label}
              </Toc.Link>
            </Toc.Item>
          ))}
        </Toc.List>
      </Toc.Nav>
    </Toc.Root>
  )
}
```

### With Rail

Give each item a small SVG offset by `depth`. Where neighbouring items sit at different depths, a bezier joins the two
positions so the rail steps rather than breaks.

```tsx
import { Toc } from '@ark-ui/react/toc'
import { useRef } from 'react'
import styles from 'styles/toc.module.css'

const items = [
  { value: '07-overview', depth: 2, label: 'Overview', lines: 10 },
  { value: '07-installation', depth: 2, label: 'Installation', lines: 8 },
  { value: '07-package-manager', depth: 3, label: 'Package Manager', lines: 12 },
  { value: '07-peer-dependencies', depth: 3, label: 'Peer Dependencies', lines: 6 },
  { value: '07-usage', depth: 2, label: 'Usage', lines: 14 },
  { value: '07-server-components', depth: 3, label: 'Server Components', lines: 9 },
  { value: '07-styling', depth: 3, label: 'Styling', lines: 11 },
  { value: '07-theming', depth: 4, label: 'Theming', lines: 7 },
  { value: '07-api-reference', depth: 2, label: 'API Reference', lines: 12 },
]

// h2 sits at level 0; deeper headings step in, clamped so h5+ share h4's indent
const BASE = 8
const RAIL_STEP = 8
const TEXT_STEP = 12
const MAX_LEVEL = 2

// the rail overlaps the row above by BRIDGE px so the turn can straddle the boundary
const BRIDGE = 6

const levelOf = (depth: number) => Math.min(Math.max(depth - 2, 0), MAX_LEVEL)
const lineOffset = (depth: number) => BASE + levelOf(depth) * RAIL_STEP
const textOffset = (depth: number) => BASE + (levelOf(depth) + 1) * TEXT_STEP

const Rail = (props: { depth: number; prevDepth?: number; nextDepth?: number }) => {
  const { depth, prevDepth = depth, nextDepth = depth } = props
  const line = lineOffset(depth)
  const prevLine = lineOffset(prevDepth)
  const nextLine = lineOffset(nextDepth)
  const turns = prevLine !== line

  return (
    <svg
      className={styles.RailSvg}
      style={{
        top: -BRIDGE,
        width: Math.max(prevLine, line) + 9,
        height: line === nextLine ? `calc(100% + ${BRIDGE}px)` : '100%',
      }}
      aria-hidden="true"
    >
      {turns && (
        <path
          d={`M ${prevLine + 0.5} 0 C ${prevLine + 0.5} 8 ${line + 0.5} 4 ${line + 0.5} ${BRIDGE * 2}`}
          fill="none"
        />
      )}
      <line x1={line + 0.5} y1={turns ? BRIDGE * 2 : BRIDGE} x2={line + 0.5} y2="100%" />
    </svg>
  )
}

export const WithRail = () => {
  const contentRef = useRef<HTMLElement | null>(null)

  return (
    <Toc.Root className={styles.Root} items={items} scrollEl={() => contentRef.current}>
      <Toc.Content className={styles.Content} ref={contentRef}>
        {items.map((item) => (
          <section key={item.value}>
            <h2 id={item.value} className={styles.Heading} data-depth={item.depth}>
              {item.label}
            </h2>
            <div className={styles.DummyText}>
              {Array.from({ length: item.lines }).map((_, i) => (
                <div key={i} className={styles.DummyLine} />
              ))}
            </div>
          </section>
        ))}
      </Toc.Content>

      <Toc.Nav className={styles.Nav}>
        <Toc.Title className={styles.Title}>On this page</Toc.Title>
        <Toc.List className={styles.RailList}>
          {items.map((item, index) => (
            <Toc.Item className={styles.RailItem} key={item.value} item={item}>
              <Toc.Link
                className={styles.RailLink}
                href={`#${item.value}`}
                style={{ paddingInlineStart: textOffset(item.depth) }}
              >
                <Rail depth={item.depth} prevDepth={items[index - 1]?.depth} nextDepth={items[index + 1]?.depth} />
                {item.label}
              </Toc.Link>
            </Toc.Item>
          ))}
        </Toc.List>
      </Toc.Nav>
    </Toc.Root>
  )
}
```

### With Tree View

Pair `Toc.Root` with `TreeView` for hierarchical navigation. `onActiveChange` expands the branch holding the active
heading.

```tsx
import { Toc, useTocContext } from '@ark-ui/react/toc'
import { TreeView, createTreeCollection } from '@ark-ui/react/tree-view'
import { ChevronRightIcon } from 'lucide-react'
import { useState, useRef } from 'react'
import tocStyles from 'styles/toc.module.css'
import treeStyles from 'styles/tree-view.module.css'

type TocNode = {
  id: string
  name: string
  depth: number
  lines: number
  children?: TocNode[]
}

const sections: TocNode[] = [
  {
    id: '09-guides',
    name: 'Guides',
    depth: 2,
    lines: 10,
    children: [
      { id: '09-quick-start', name: 'Quick Start', depth: 3, lines: 6 },
      { id: '09-manual-setup', name: 'Manual Setup', depth: 3, lines: 5 },
    ],
  },
  {
    id: '09-core-concepts',
    name: 'Core Concepts',
    depth: 2,
    lines: 9,
    children: [
      { id: '09-toc-props', name: 'Props', depth: 3, lines: 7 },
      { id: '09-toc-events', name: 'Events', depth: 3, lines: 6 },
      { id: '09-toc-context', name: 'Context', depth: 3, lines: 8 },
    ],
  },
  {
    id: '09-advanced',
    name: 'Advanced',
    depth: 2,
    lines: 11,
    children: [
      { id: '09-root-api', name: 'Root Provider', depth: 3, lines: 7 },
      { id: '09-custom-rendering', name: 'Custom Rendering', depth: 3, lines: 6 },
    ],
  },
]

const collection = createTreeCollection<TocNode>({
  nodeToValue: (node) => node.id,
  nodeToString: (node) => node.name,
  rootNode: { id: 'ROOT', name: '', depth: 0, lines: 0, children: sections },
})

const allItems = sections.flatMap((section) => [
  { value: section.id, depth: section.depth },
  ...(section.children ?? []).map((child) => ({ value: child.id, depth: child.depth })),
])

const TocTreeNode = ({ node, indexPath }: TreeView.NodeProviderProps<TocNode>) => {
  const toc = useTocContext()
  return (
    <TreeView.NodeProvider node={node} indexPath={indexPath}>
      {node.children ? (
        <TreeView.Branch className={treeStyles.Branch}>
          <TreeView.BranchControl className={treeStyles.BranchControl}>
            <TreeView.BranchIndicator className={treeStyles.BranchIndicator}>
              <ChevronRightIcon />
            </TreeView.BranchIndicator>
            <TreeView.BranchText className={treeStyles.BranchText}>
              <a className={tocStyles.TreeLink} {...toc.getLinkProps({ item: { value: node.id, depth: node.depth } })}>
                {node.name}
              </a>
            </TreeView.BranchText>
          </TreeView.BranchControl>
          <TreeView.BranchContent className={treeStyles.BranchContent}>
            <TreeView.BranchIndentGuide className={treeStyles.BranchIndentGuide} />
            {node.children.map((child, index) => (
              <TocTreeNode key={child.id} node={child} indexPath={[...indexPath, index]} />
            ))}
          </TreeView.BranchContent>
        </TreeView.Branch>
      ) : (
        <TreeView.Item className={treeStyles.Item}>
          <TreeView.ItemText className={treeStyles.ItemText}>
            <a className={tocStyles.TreeLink} {...toc.getLinkProps({ item: { value: node.id, depth: node.depth } })}>
              {node.name}
            </a>
          </TreeView.ItemText>
        </TreeView.Item>
      )}
    </TreeView.NodeProvider>
  )
}

export const WithTreeView = () => {
  const [expandedValue, setExpandedValue] = useState<string[]>([])
  const contentRef = useRef<HTMLElement | null>(null)

  return (
    <Toc.Root
      className={tocStyles.Root}
      items={allItems}
      scrollEl={() => contentRef.current}
      onActiveChange={({ activeItems }) => {
        const activeIds = new Set(activeItems.map((i) => i.value))
        const next = sections
          .filter(
            (section) => activeIds.has(section.id) || (section.children ?? []).some((child) => activeIds.has(child.id)),
          )
          .map((s) => s.id)
        setExpandedValue(next)
      }}
    >
      <Toc.Content className={tocStyles.Content} ref={contentRef}>
        {sections.map((section) => (
          <section key={section.id}>
            <h2 id={section.id} className={tocStyles.Heading} data-depth={section.depth}>
              {section.name}
            </h2>
            <div className={tocStyles.DummyText}>
              {Array.from({ length: section.lines ?? 5 }, (_, i) => (
                <div key={i} className={tocStyles.DummyLine} />
              ))}
            </div>
            {section.children?.map((child) => (
              <div key={child.id}>
                <h3 id={child.id} className={tocStyles.Heading} data-depth={child.depth}>
                  {child.name}
                </h3>
                <div className={tocStyles.DummyText}>
                  {Array.from({ length: child.lines ?? 3 }, (_, i) => (
                    <div key={i} className={tocStyles.DummyLine} />
                  ))}
                </div>
              </div>
            ))}
          </section>
        ))}
      </Toc.Content>

      <Toc.Nav className={tocStyles.Nav}>
        <Toc.Title className={tocStyles.Title}>On this page</Toc.Title>
        <TreeView.Root
          className={treeStyles.Root}
          collection={collection}
          expandedValue={expandedValue}
          onExpandedChange={({ expandedValue: next }) => setExpandedValue(next)}
        >
          <TreeView.Tree className={treeStyles.Tree}>
            {sections.map((node, index) => (
              <TocTreeNode key={node.id} node={node} indexPath={[index]} />
            ))}
          </TreeView.Tree>
        </TreeView.Root>
      </Toc.Nav>
    </Toc.Root>
  )
}
```

## Guides

### Items

Every entry needs `value`, the `id` of the heading element, and `depth`, the heading level.

```tsx
const items = [
  { value: 'introduction', depth: 2 },
  { value: 'installation', depth: 2 },
  { value: 'peer-dependencies', depth: 3 },
]
```

`value` must match the heading's `id` exactly. The component resolves it with `getElementById` to track visibility, and
`Toc.Link` targets it with `href="#introduction"`. An item whose id is missing renders but never activates.

Ids are global to the page, so prefix them when a page holds more than one TOC.

Extra properties are fine, a `label` for link text being the common one. `TocItemData` covers only `value` and `depth`,
so extend it rather than annotating with it directly:

```tsx
import type { TocItemData } from '@ark-ui/react/toc'

interface Item extends TocItemData {
  label: string
}
```

## API Reference

### Props

### Root

#### Props

**`items`**
Type: `TocItem[]`
Required: true
Default Value: `undefined`
Description: The TOC items with `value` (slug/id) and `depth` (heading level).

**`activeIds`**
Type: `string[]`
Required: false
Default Value: `undefined`
Description: The controlled active heading ids.

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`autoScroll`**
Type: `boolean`
Required: false
Default Value: `true`
Description: Whether to auto-scroll the TOC container so the first active item
is visible when active headings change.

**`defaultActiveIds`**
Type: `string[]`
Required: false
Default Value: `undefined`
Description: The default active heading ids when rendered.
Use when you don't need to control the active headings.

**`id`**
Type: `string`
Required: false
Default Value: `undefined`
Description: The unique identifier of the machine.

**`ids`**
Type: `Partial<{
  root: string
  title: string
  list: string
  item: (value: string) => string
  link: (value: string) => string
  indicator: string
}>`
Required: false
Default Value: `undefined`
Description: The ids of the elements in the TOC. Useful for composition.

**`onActiveChange`**
Type: `(details: ActiveChangeDetails) => void`
Required: false
Default Value: `undefined`
Description: Callback when the active (visible) headings change.

**`rootMargin`**
Type: `string`
Required: false
Default Value: `"-20px 0px -40% 0px"`
Description: The root margin for the IntersectionObserver.
Controls the effective viewport area for determining active headings.

**`scrollBehavior`**
Type: `ScrollBehavior`
Required: false
Default Value: `"smooth"`
Description: The default scroll behavior used when auto-scrolling the TOC container
and when scrolling to a heading (via link click or `api.scrollTo`).
Can be overridden per-call by passing `behavior` to `api.scrollTo`.

**`scrollEl`**
Type: `() => HTMLElement | null`
Required: false
Default Value: `undefined`
Description: Function that returns the scroll container element to observe within.
Defaults to the document/viewport.

**`threshold`**
Type: `number | number[]`
Required: false
Default Value: `0`
Description: The IntersectionObserver threshold. A value of `0` means the heading is
active as soon as even one pixel is visible within the root margin area.

### Content

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### Indicator

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### Item

#### Props

**`item`**
Type: `TocItem`
Required: true
Default Value: `undefined`
Description: The TOC item

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: toc
**`data-part`**: item
**`data-value`**: The value of the item
**`data-depth`**: The depth of the item
**`data-active`**: Present when active or pressed
**`data-first`**: 
**`data-last`**: 

### Link

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

#### Data Attributes

**`data-scope`**: toc
**`data-part`**: link
**`data-value`**: The value of the item
**`data-active`**: Present when active or pressed

### List

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### Nav

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

**`placement`**
Type: `'left' | 'right'`
Required: false
Default Value: `undefined`
Description: undefined

### RootProvider

#### Props

**`value`**
Type: `UseTocReturn`
Required: true
Default Value: `undefined`
Description: undefined

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### Title

#### Props

**`asChild`**
Type: `boolean`
Required: false
Default Value: `undefined`
Description: Use the provided child element as the default rendered element, combining their props and behavior.

### Context

**API:**

| Property | Type | Description |
|----------|------|-------------|
| `activeIds` | `string[]` | All currently active (visible) heading ids |
| `activeItems` | `TocItem[]` | The active (visible) TOC items |
| `items` | `TocItem[]` | The resolved items list |
| `setActiveIds` | `(value: string[]) => void` | Manually set the active heading ids |
| `scrollTo` | `(value: string, details?: ScrollToDetails | undefined) => void` | Scrolls to the heading with the given id. |
| `getItemState` | `(props: ItemProps) => ItemState` | Returns the state of a TOC item |
