Skip to content
shadcn.io
shadcn.io is not affiliated with official shadcn/ui

Shadcn Table of Contents for React and Tailwind

Highlights the heading in view on docs and long pages.

Table of Contents anatomy: root, title, indicator, item, link

Introduction

Getting Started

Installation

Usage

Conclusion

Installation

bunx --bun shadcn@latest add https://kit.dev/r/toc.json

Anatomy

Toc
├── TocContext
├── TocContent
└── TocNav
    ├── TocTitle
    ├── TocList
    │   ├── TocIndicator
    │   └── TocItem
    │       └── TocLink
    └── TocScrollToTop

Usage

import {
  Toc,
  TocContent,
  TocIndicator,
  TocItem,
  TocLink,
  TocList,
  TocNav,
  TocScrollToTop,
  TocTitle,
} from "@/components/ui/toc"
<Toc items={items} scrollEl={() => contentRef.current}>
  <TocContent ref={contentRef}>
    <h2 id="introduction">Introduction</h2>
  </TocContent>
  <TocNav scrollToTop>
    <TocTitle>On this page</TocTitle>
    <TocList>
      <TocIndicator />
      {items.map((item) => (
        <TocItem item={item} key={item.value}>
          <TocLink href={`#${item.value}`}>{item.label}</TocLink>
        </TocItem>
      ))}
    </TocList>
  </TocNav>
</Toc>

Guides

Items

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

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 TocLink 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:

import type { TocItemData } from "@/components/ui/toc"

interface Item extends TocItemData {
  label: string
}

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

Nested Headings

Read depth in your own markup to indent sub-headings. Nothing is indented for you beyond the default data-depth padding on TocItem.

Root Provider

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

Examples

With Collapsible

Wrap the navigation in a Collapsible to let users hide it. TocContext exposes activeItems, here driving a Circular Progress ring.

With Hover

Expand the navigation on onMouseEnter and collapse it on onMouseLeave.

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

With Indicator

Add TocIndicator inside TocList for a marker that slides to the active item.

With Rail

Each link draws its own SVG rail, offset by depth. Neighbouring items at different depths connect with a cubic turn. The active link colors its stroke.

With Select

Jump with a Native Select driven by TocContext. Useful on small viewports in place of TocNav.

With Tree View

Pair Toc with Tree View for hierarchical navigation. Folders only expand or collapse — they do not jump the page. Leaf rows jump to the heading, and the tree's selected item is the indicator. onActiveChange opens the branch that holds the heading in view.

API Reference

shadcn.io wraps Ark UI Toc. asChild merges props onto a single child element.

Toc

Root. Renders a div.

PropTypeDefaultDescription
itemsTocItemData[]requiredHeadings to track. Each item needs value (the heading id) and depth.
asChildbooleanfalseRender the child element instead of a div.
classNamestring-Class names on the root.
activeIdsstring[]-Controlled active heading ids.
autoScrollbooleantrueScroll the TOC so the first active item stays in view.
defaultActiveIdsstring[]-Uncontrolled active heading ids.
dir"ltr" | "rtl"-Text direction. Usually inherited from LocaleProvider.
idstring-Unique id for the toc machine.
idsPartial<{ root: string; title: string; list: string; item: (value: string) => string; link: (value: string) => string; indicator: string }>-Element ids for composition.
onActiveChange(details: TocActiveChangeDetails) => void-Called when visible headings change. TocActiveChangeDetails is { activeIds: string[]; activeItems: TocItemData[] }.
rootMarginstring"-20px 0px -40% 0px"IntersectionObserver root margin.
scrollBehaviorScrollBehavior"smooth"Used for auto-scroll and scrollTo.
scrollEl() => HTMLElement | null-Scroll container to observe. Defaults to the document.
thresholdnumber | number[]0IntersectionObserver threshold.
AttributeDescription
data-slottoc
data-scopetoc
data-partroot
CSS variableDescription
--topActive item offset from the list. Used by TocIndicator.
--heightActive item height. Used by TocIndicator.
--leftActive item inline offset from the list.
--widthActive item width.

TocContent

Scrollable article that holds the headings. Renders an article. Point scrollEl at this node in demos.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child element.
classNamestring-Class names on the article.
AttributeDescription
data-slottoc-content

TocNav

Sticky navigation column. Renders a nav.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child element.
classNamestring-Class names on the nav.
placement"left" | "right"-Sets data-placement. left moves the nav before the content in flex order.
scrollToTopbooleanfalseRender TocScrollToTop after the nav children.
AttributeDescription
data-slottoc-nav
data-placement"left" or "right" when set

TocTitle

Label for the nav. Renders an h2. Referenced by aria-labelledby on the root.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child element.
classNamestring-Class names on the title.
AttributeDescription
data-slottoc-title
data-scopetoc
data-parttitle

TocList

List of heading links. Renders a ul. Keep TocIndicator inside this list.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child element.
classNamestring-Class names on the list.
AttributeDescription
data-slottoc-list
data-scopetoc
data-partlist

TocItem

One heading row. Renders an li.

PropTypeDefaultDescription
itemTocItemDatarequiredThe heading this row represents.
asChildbooleanfalseMerge onto a single child element.
classNamestring-Class names on the item.
AttributeDescription
data-slottoc-item
data-scopetoc
data-partitem
data-valueHeading id
data-depthHeading level
data-activePresent when this heading is in view
data-firstPresent on the first active item
data-lastPresent on the last active item
CSS variableDescription
--depthHeading level. Use for custom indentation.

Link to a heading. Renders an a. Reads the parent TocItem. Set href to # plus item.value.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child element.
classNamestring-Class names on the link.
AttributeDescription
data-slottoc-link
data-scopetoc
data-partlink
data-valueHeading id
data-activePresent when this heading is in view
aria-current"location" when active

TocIndicator

Marker that tracks the active item. Renders a div. Place it inside TocList.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child element.
classNamestring-Class names on the indicator.
AttributeDescription
data-slottoc-indicator
data-scopetoc
data-partindicator

TocScrollToTop

Button that appears after the reader has scrolled. Clicking it scrolls the tracked container to the top and activates the first heading.

Use scrollToTop on TocNav, or render TocScrollToTop yourself to change the label.

PropTypeDefaultDescription
classNamestring-Class names on the button.
offsetnumber180Scroll distance in pixels before the button shows.
AttributeDescription
data-slottoc-scroll-to-top

TocRootProvider

Root alternative that takes the API from useToc. Renders a div.

PropTypeDefaultDescription
valueUseTocReturnrequiredReturn value of useToc().
asChildbooleanfalseRender the child element instead of a div.
classNamestring-Class names on the root.

Pass items, scrollEl, and the other machine options to useToc(), not to TocRootProvider.

AttributeDescription
data-slottoc
data-scopetoc
data-partroot

useToc

Creates the toc API for TocRootProvider. Accepts the same options as Toc except asChild and className.

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

TocContext / useTocContext

Render-prop or hook access to toc state. Use inside Toc or TocRootProvider.

PropertyTypeDescription
activeIdsstring[]Ids of headings currently in view.
activeItemsTocItemData[]Active items.
itemsTocItemData[]Resolved items list.
scrollTo(value: string, details?: { behavior?: ScrollBehavior }) => voidScroll the tracked container to a heading.
setActiveIds(value: string[]) => voidSet active heading ids.
getItemState(props: { item: TocItemData }) => ItemStateState for one item (active, first, last, depth).
getLinkProps(props: { item: TocItemData }) => HTMLPropsProps for a custom heading link.

TocContext children: (context) => ReactNode.

Accessibility

The root is labelled by TocTitle. Active links set aria-current="location".

Keyboard support

KeyDescription
TabMove focus to the next link.
Shift + TabMove focus to the previous link.
EnterActivate the focused link and scroll to that heading.