Table of Contents
abui/tocFreeComponent
A table of contents component with active anchor tracking and depth-aware line styling. Features two variants: default (straight line) and clerk (depth-responsive line that follows hierarchy).
Live preview
live · rendered by the registry
Rendered by abui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://abui.io/r/toc.jsonSource
1"use client"23import * as React from "react"4import {5 createContext,6 useContext,7 useRef,8 useState,9 useEffect,10 useMemo,11 useLayoutEffect,12 type ReactNode,13 type RefObject,14 type ComponentProps,15 type HTMLAttributes,16} from "react"17import { cn } from "@/lib/utils"1819// ============================================================================20// Types21// ============================================================================2223export interface TOCItemType {24 title: ReactNode25 url: string26 depth: number27}2829export type TableOfContents = TOCItemType[]3031// ============================================================================32// Contexts33// ============================================================================3435const ActiveAnchorContext = createContext<string[]>([])36const ScrollContext = createContext<RefObject<HTMLElement | null>>({ current: null })37const TOCContext = createContext<TOCItemType[]>([])3839// ============================================================================40// Hooks41// ============================================================================4243/**44 * The id of visible anchors45 */46export function useActiveAnchors(): string[] {47 return useContext(ActiveAnchorContext)48}4950/**51 * The estimated active heading ID52 */53export function useActiveAnchor(): string | undefined {54 return useContext(ActiveAnchorContext)[0]55}5657/**58 * Get TOC items from context59 */60export function useTOCItems(): TOCItemType[] {61 return useContext(TOCContext)62}6364// ============================================================================65// Utility Functions66// ============================================================================6768function mergeRefs<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {69 return value => {70 refs.forEach(ref => {71 if (typeof ref === "function") {72 ref(value)73 } else if (ref != null) {74 ;(ref as React.MutableRefObject<T | null>).current = value75 }76 })77 }78}7980/**81 * Find the active heading of page using IntersectionObserver82 */83function useAnchorObserver(watch: string[], single: boolean): string[] {84 const observerRef = useRef<IntersectionObserver | null>(null)85 const [activeAnchor, setActiveAnchor] = useState<string[]>([])86 const stateRef = useRef<{ visible: Set<string> } | null>(null)8788 const onChange = React.useCallback(89 (entries: IntersectionObserverEntry[]) => {90 stateRef.current ??= { visible: new Set() }91// … truncated
Files it writes
More from abui
All 20 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordion Multiselectaccordion-multiselect | abui | Components | Free | 3 deps | |
| Breakpoint Displaybreakpoint-display | abui | Components | Free | no deps | |
| Color Swatch Selectorcolor-swatch-selector | abui | Components | Free | no deps | |
| Label Selectorlabel-selector | abui | Components | Free | 2 deps | |
| Radio Rowsradio-rows | abui | Components | Free | no deps | |
| Radio Tabsradio-tabs | abui | Components | Free | no deps | |
| Scroll Progressscroll-progress | abui | Components | Free | 1 dep | |
| Spinnerspinner | abui | Components | Free | 1 dep |
