Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

Navigation

Table Of Contents

Navigation

Table Of Contents React component for Tailwind CSS v4

StepsTabs
Live props
Content
headings
title
pillLabel
sheetTitle
State
defaultSheetOpen
Appearance
variant
size
tone
color
showProgress
Layout
collapseBelow
collapseOn
sticky
Behavior
offset16
smooth
updateHash
Code
import { TableOfContents } from "@/components/ui/table-of-contents"

<TableOfContents />
  1. Home
  2. Components
  3. Navigation
  4. Table Of Contents
Pro

Table Of Contents React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React table of contents that lists a page's sections, highlights the one being read and jumps to any, with a pill and sheet on phones.

Scroll the page and the section you are reading lights up: on the line variant a short bar glides along the rail to it on a smooth spring, on the dots variant its dot grows and fills. Click a row to glide to that section; the highlight goes straight to it instead of running through the sections in between, the heading lands just under the top bar, receives focus and the URL hash updates (without adding history). Tab or the arrow keys (Home / End for the first and last) move through the rows, Enter jumps. Opening a page with #section in the URL lands there without animation. Below collapseBelow the list turns into a sticky pill under the top bar with a reading ring and the current section, which crossfades as you scroll; tap it to open every section in a bottom sheet (48px rows, the current one checked), tap a row and the sheet closes, then the page jumps. Under reduced motion jumps are instant and nothing slides or crossfades.

Table Of Contents is a Pro React component in the React navigation components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add table-of-contents, together with UIProvider, Drawer, useControllableState, useMediaQuery, useScrollState and 3 more and the npm packages motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add table-of-contents

Table Of Contents is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import TableOfContents from @/components/ui/table-of-contents.

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

export function Contract() {
  return (
    <div className="mx-auto max-w-5xl px-5 lg:grid lg:grid-cols-[minmax(0,1fr)_220px] lg:gap-12">
      <TableOfContents
        title="On this page"
        pillLabel="Contents"
        sheetTitle="Contract contents"
        className="pt-3 lg:col-start-2 lg:row-start-1 lg:pt-8"
      />
      <article className="lg:col-start-1 lg:row-start-1">
        <h1>Lease agreement</h1>
        <h2>1. The parties</h2>
        <p>Landlord: Olivia Bennett. Tenant: Andrew Collins.</p>
        <h2>4. Rent and payment</h2>
        <p>The monthly rent is $2,500.00.</p>
        <h3>4.1 Security deposit</h3>
        <p>One month of rent, returned within 15 days.</p>
      </article>
    </div>
  )
}

Props

  • items
    TocItem[]

    { id: string; label: ReactNode; level?: 2 | 3; items?: TocItem[] }. Nested items or level: 3 rows become subsections. Leave empty to collect headings from the page. Ids missing in the DOM keep their row, jumping to them does nothing.

  • headingsdefault "h2, h3"
    string

    Selector used to collect items when items is empty, after mount and again (debounced 150ms) whenever the content changes. Headings without an id get one from their text; data-toc-ignore skips one, data-toc-label renames it.

  • container
    HTMLElement | RefObject<HTMLElement | null> | null

    Where headings are collected from. Default: the scroll container, else <main>, else the body.

  • titledefault "On this page"
    ReactNode

    The eyebrow above the list, also the nav's aria-label when it is a string. "" hides it.

  • pillLabeldefault "Contents"
    string

    The muted word on the phone pill before the current section.

  • sheetTitledefault "On this page"
    string

    The title of the bottom sheet the pill opens. Also names the nav landmark when title is empty or not a string.

  • defaultSheetOpendefault false
    boolean

    Opens the phone sheet on mount, uncontrolled. The sheet only opens below collapseBelow, where its pill shows.

  • sheetOpen
    boolean

    The phone sheet's open state, controlled. Pair it with onSheetOpenChange.

  • onSheetOpenChange
    (open: boolean) => void

    Called when the sheet opens or closes.

  • variantdefault "line"
    "line" | "dots" | "plain"

    line: a rail with a segment that glides to the active row. dots: a dot per row that grows and fills. plain: text only, the active row in medium weight.

  • sizedefault "sm"
    "sm" | "md"

    Rows at 13 / 14px. Every row is 44px tall on touch screens.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    Colors the indicator, the active row, the dots and the progress ring.

  • colordefault ""
    string

    Any CSS color. Replaces the tone. Leave empty to use the tone.

  • showProgressdefault false
    boolean

    A 16px reading-progress ring and a percentage next to the title. Both follow the scroll every frame without re-rendering. The phone pill always has the ring.

  • collapseBelowdefault "lg"
    "md" | "lg" | "xl" | "never" | "always"

    Below this width the list becomes a sticky pill that opens a sheet. Pure CSS, both are rendered, so the server HTML is right on a phone. always: the pill at every width (narrow columns, phone layouts).

  • collapseOndefault "viewport"
    "viewport" | "container"

    What collapseBelow measures. viewport: media queries (768 / 1024 / 1280px). container: the nearest ancestor with the @container class, at the same widths, for pages that sit in a narrow pane, a card or a split view. Lay out the columns with the matching @3xl: / @5xl: / @7xl: variants.

  • stickydefault true
    boolean

    Pins the list 24px and the pill 8px under the top bar (--topbar-offset) while the page scrolls. Place it as a direct child of the layout that spans the whole page (a grid column, or before the article).

  • offsetdefault 16
    number

    Pixels below the top bar where the reading line sits and where a jumped-to heading lands. --topbar-offset, and the pill's height when it shows, are added automatically. The demo's readout shows it.

  • smoothdefault true
    boolean

    Smooth jumps. Always instant when the user prefers reduced motion. The demo's readout shows it.

  • updateHashdefault true
    boolean

    A jump writes #id into the URL with history.replaceState, so it can be shared without adding history entries. Scrolling never does. The demo's readout shows it.

  • scrollTarget
    HTMLElement | Window | RefObject<HTMLElement | null> | null

    What scrolls. Default: the nearest ScrollContainerProvider, else the headings' scrolling ancestor, else the window.

  • onActiveChange
    (id: string | null) => void

    Called with the id of the section being read whenever it changes.

  • classNames
    Partial<Record<"header" | "title" | "progress" | "scroll" | "list" | "item" | "link" | "rail" | "indicator" | "dot" | "pill" | "pillLabel" | "pillRing" | "sheet" | "sheetItem", string>>

    Extra classes for the inner parts. scroll is the box that scrolls a list taller than the screen.

  • className
    string

    Extra classes for the <nav> root. Other props go to it too.

Parts

  • useActiveHeading

    hook

    The scroll spy on its own, for a custom table of contents: the id of the heading being read. Signature: (ids: string[], { root?: ScrollTarget; offset?: number; disabled?: boolean }) => string | null.

  • collectHeadings

    function

    The headings of a container as items, in order, assigning slug ids (duplicates suffixed -2) to headings without one. Signature: (container: ParentNode | null, selector?: string) => TocItem[].

Examples

Three variants

line with a moving indicator, dots, and plain text, all following the same page.

Collected headings

No items: the h2 and h3 of a product spec page are collected, given ids and kept up to date.

Reading progress

showProgress adds a ring and a percentage next to the title of a long help article.

Settings left rail

A plain md list on the left of a settings page, with level 2 and 3 sections.

Phone: pill and sheet

collapseBelow="always": a sticky pill with the current section opens every section in a bottom sheet.

Custom color

color="#0d9488" paints the indicator, the active row and the progress ring.

Dependencies

motionlucide-reactui-configdraweruse-controllable-stateuse-media-queryuse-scroll-statemotiontext-matchtones

Files

  • components/ui/table-of-contents.tsx

FAQ

Is the React Table Of Contents component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Table Of Contents?

Run npx wingo-ui@latest add table-of-contents. It also adds the Wingo UI items it builds on (UIProvider, Drawer, useControllableState, useMediaQuery, useScrollState and 3 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/table-of-contents.

How does Table Of Contents behave on phones?

Below collapseBelow the list turns into a sticky pill under the top bar with a reading ring and the current section, which crossfades as you scroll; tap it to open every section in a bottom sheet (48px rows, the current one checked), tap a row and the sheet closes, then the page jumps.

See also

useScrollStatehookScroll ProgressHook SidebarProximity SidebarDrawer

Used in

Blog Post PageCustomer Story PageHelp Article PageLegal Page

More React navigation components

TabsTopbarWorkspace SwitcherBreadcrumbBounce SidebarGooey Nav
All 19 React navigation components

Articles using this component

  • Nextjs Blog Template: Markdown, Live Demos and JSON-LD

    Build it: screens and apps · Oct 16, 2026 · 10 min read

PreviousStepsNext Tabs
NNorthwind CRM / Documents / Contracts#none· 16px · smooth · hash

On this page

Documents · Contracts

Lease agreement, 2-bedroom apartment, New York

8 min read · no. 118 of Sep 23, 2026

1. The parties

Landlord: Olivia Bennett, residing at 14 Observatory Lane, New York, hereinafter the Landlord.

Tenant: Andrew Collins, residing at 7 Pine Street, Seattle, hereinafter the Tenant. The parties agree to this lease on the terms below.

2. The property

The Landlord rents to the Tenant the 2-bedroom apartment at 22 West 86th Street, apt. 9, 3rd floor, New York, with a floor area of 580 sq ft.

The apartment is handed over furnished and equipped as listed in the move-in report, annex 1 to this lease, signed by both parties on October 1, 2026.

3. Term of the lease

The lease runs for 12 months, from October 1, 2026 to September 30, 2027. At the end it can be extended by an addendum, with the consent of both parties.

The Tenant gives notice of an extension at least 30 days before the lease ends.

4. Rent and payment

The monthly rent is $2,500.00, due by the 5th of each month by bank transfer to account 0130 1202 4512, held at Chase.

Payments more than 10 days late carry a penalty of 0.1% of the amount due per day.

4.1 Security deposit

On signing, the Tenant pays a security deposit equal to one month of rent, $2,500.00. It is returned within 15 days of moving out, minus any damage or unpaid amounts.

4.2 Utilities

Electricity, gas, water, internet and building fees are paid by the Tenant, based on the providers' bills. The meter readings at move-in are listed in annex 1.

5. Landlord obligations

To hand over the apartment in good condition, to ensure its quiet use for the whole term and to carry out major repairs, except those caused by the Tenant.

To register the lease with the local housing authority within 30 days.

6. Tenant obligations

To use the apartment with care, to pay the rent and utilities on time, not to sublet without the Landlord's written consent and to allow viewings with 48 hours' notice.

To return the apartment at the end of the lease in the condition it was received, allowing for normal wear.

7. Ending the lease

The lease ends when its term expires, by mutual agreement or by either party with 30 days' notice. Two consecutive months of unpaid rent allow the Landlord to terminate it.

8. Final provisions

This lease was signed today, September 23, 2026, in New York, in two original copies, one for each party. Disputes are settled amicably, otherwise by the courts of New York.

Annexes: 1. Move-in report. 2. Copies of the parties' IDs.

Landlord
Olivia Bennett

Tenant
Andrew Collins

Scroll the contract: the section you are reading lights up. Click a row to jump there; on a phone, tap the pill.