# Table of contents (goodtabs UI)

An on this page list that follows the reader and marks the active heading.

## Install

```bash
npx shadcn@latest add https://ui.goodtabs.co/r/table-of-contents.json
```

Or, with the @goodtabs namespace configured in components.json: `npx shadcn@latest add @goodtabs/table-of-contents`

## Usage

```tsx
import { TableOfContents } from "@/components/ui/table-of-contents"

<TableOfContents
  offset={64}
  items={[
    { id: "install", label: "Install", level: 2 },
    { id: "usage", label: "Usage", level: 2 },
    { id: "props", label: "Props", level: 3 },
  ]}
/>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| items | `{ id: string; label: string; level?: 2 | 3 }[]` |  | The headings in page order. id must match the heading's id attribute. |
| title | `string | null` | `"On this page"` | Small mono heading above the list. null hides it. |
| activeId | `string` |  | Controlled active id. Set it and the scroll spy switches off. |
| offset | `number` | `0` | Pixels from the top of the scroll area before a heading counts as active. |
| root | `RefObject<HTMLElement | null>` |  | A scrolling element to spy on and scroll, instead of the window. |

## When to use

- The right rail of a docs page, a long article or a changelog with four or more headings
- When readers need to see where they are and jump without scrolling
- Inside a scrolling panel, with root pointing at it

## When not to use

- For a page with fewer than three headings: the rail is noise
- For navigating between pages, use a sidebar or section nav
- In a phone layout: hide it under lg or move it into a sheet

## Notes for agents

- Client component. The headings must already carry the ids you list; it does not generate them
- The spy watches the top 40% of the scroll area: the first listed heading inside it wins, the last winner stays between headings
- With root set, clicks scroll that element instead of the page; without it the links are plain anchors
- Set offset to the height of any sticky header, and add scroll-mt to the headings for the anchor fallback

Docs: https://ui.goodtabs.co/components/table-of-contents