Table of Contents
Accessible, unstyled table of contents that tracks the section in view.
|
|
| Package |
@vizejs/ui/toc |
| Maturity |
stable |
| Own the source |
vize lib pull toc |
| Requires |
collection, context, controllable-state, id, scroll-spy |
| Aliases |
table of contents, toc, on this page, outline navigation, anchor navigation |
| Covers |
HTML nav landmark, aria-current location, Docusaurus TOC, Nuxt UI ContentToc |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { Toc, TocRoot, TocItem, TocLink, TocList } from "@vizejs/ui/toc";
Or copy the source into your project with vize lib pull toc (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Table of contents that links to article headings and tracks the active section. -->
<script setup lang="ts">
import { useId } from "vue";
import { TocItem, TocLink, TocList, TocRoot } from "@vizejs/ui/toc";
const sections = [
{ id: useId(), title: "Installation" },
{ id: useId(), title: "Configuration" },
{ id: useId(), title: "Deployment" },
];
</script>
<template>
<div>
<TocRoot aria-label="On this page">
<TocList>
<TocItem v-for="section in sections" :key="section.id" :target-id="section.id">
<TocLink :target-id="section.id">{{ section.title }}</TocLink>
</TocItem>
</TocList>
</TocRoot>
<article>
<section v-for="section in sections" :key="section.id">
<h2 :id="section.id">{{ section.title }}</h2>
<p>Details about {{ section.title.toLowerCase() }}.</p>
</section>
</article>
</div>
</template>
API
Toc
Source: src/families/navigation/toc/toc-root.vue
Props
| Prop |
Type |
Default |
Description |
activeId |
string | null |
undefined |
Controlled active section id (v-model:activeId). undefined lets the scroll spy own it. |
defaultActiveId |
string | null |
null |
Active id rendered on the server and before the first measurement. |
offset |
number |
0 |
Activation line in CSS pixels below the scroll container top, such as a sticky header height. |
root |
Element | null |
undefined |
Scroll container holding the sections. undefined tracks the document viewport. |
track |
boolean |
true |
Track scroll position. Disable it to drive activeId entirely from outside. |
scrollBehavior |
TocScrollBehavior |
undefined |
Script-driven scrolling for link clicks. undefined keeps native fragment navigation. |
updateHash |
boolean |
true |
Replace the URL fragment after script-driven scrolling. |
ariaLabel |
string |
"Table of contents" |
Accessible name of the navigation landmark. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the landmark instead of ariaLabel. |
Events
| Event |
Payload |
Description |
update:activeId |
[id: string | null] |
Fired with the section id that came into view or was navigated to. |
navigate |
[id: string, nativeEvent: MouseEvent] |
Fired when a link is activated, before any script-driven scrolling. |
Slots
| Slot |
Slot props |
Description |
default |
TocSlotState |
TocList, TocItem, and TocLink children. Receives the active id and tracked ids. |
Exposed
| Member |
Type |
Description |
activeId |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
refresh |
— |
|
scrollTo |
— |
|
TocRoot
Source: src/families/navigation/toc/toc-root.vue
Props
| Prop |
Type |
Default |
Description |
activeId |
string | null |
undefined |
Controlled active section id (v-model:activeId). undefined lets the scroll spy own it. |
defaultActiveId |
string | null |
null |
Active id rendered on the server and before the first measurement. |
offset |
number |
0 |
Activation line in CSS pixels below the scroll container top, such as a sticky header height. |
root |
Element | null |
undefined |
Scroll container holding the sections. undefined tracks the document viewport. |
track |
boolean |
true |
Track scroll position. Disable it to drive activeId entirely from outside. |
scrollBehavior |
TocScrollBehavior |
undefined |
Script-driven scrolling for link clicks. undefined keeps native fragment navigation. |
updateHash |
boolean |
true |
Replace the URL fragment after script-driven scrolling. |
ariaLabel |
string |
"Table of contents" |
Accessible name of the navigation landmark. |
ariaLabelledby |
string |
undefined |
Space-separated ids that label the landmark instead of ariaLabel. |
Events
| Event |
Payload |
Description |
update:activeId |
[id: string | null] |
Fired with the section id that came into view or was navigated to. |
navigate |
[id: string, nativeEvent: MouseEvent] |
Fired when a link is activated, before any script-driven scrolling. |
Slots
| Slot |
Slot props |
Description |
default |
TocSlotState |
TocList, TocItem, and TocLink children. Receives the active id and tracked ids. |
Exposed
| Member |
Type |
Description |
element |
HTMLElement | null |
Rendered navigation landmark. |
activeId |
string | null |
Id of the section currently in view. |
scrollTo |
(id: string) => boolean |
Scroll a target into view and make it active. |
refresh |
() => void |
Re-measure targets immediately. |
TocItem
Source: src/families/navigation/toc/toc-item.vue
Props
| Prop |
Type |
Default |
Description |
targetId |
string |
undefined |
Target id of this entry. Supply it for exact server-rendered data-active; otherwise it is learned from the nested TocLink after it renders. |
Slots
| Slot |
Slot props |
Description |
default |
TocLinkSlotState |
TocLink and optional nested TocList. Receives the link target and active state. |
TocLink
Source: src/families/navigation/toc/toc-link.vue
Props
| Prop |
Type |
Default |
Description |
targetId (required) |
string |
required |
Id of the heading or section this link points to. |
Slots
| Slot |
Slot props |
Description |
default |
TocLinkSlotState |
Link text. Receives the target id and whether its section is in view. |
Exposed
| Member |
Type |
Description |
active |
— |
|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
TocList
Source: src/families/navigation/toc/toc-list.vue
Props
| Prop |
Type |
Default |
Description |
level |
number |
1 |
Nesting depth exposed to styles; nested lists live inside a TocItem. |
Slots
| Slot |
Slot props |
Description |
default |
{ /** Nesting depth of this list. */ readonly level: number; } |
TocItem children. |
Behavior
Normative state x input -> outcome table for toc-root.vue, toc-list.vue,
toc-item.vue, and toc-link.vue (@vizejs/ui/toc), plus
collectTocEntries. Every row is proven by the named test in toc.test.ts or
toc-ssr.test.ts; compile-only guarantees live in toc.types.test-d.ts.
The root is a labelled <nav> landmark with nested <ol> lists of native
fragment links. The link whose section is in view carries
aria-current="location". Active tracking reuses the @vizejs/ui/scroll-spy
composable over the ids of the registered links.
| ID |
State |
Input |
Outcome |
Evidence |
| C1 |
default |
render |
labelled nav, nested ol lists, #fragment links, aria-current on the default id |
renders a labelled navigation landmark with fragment links |
| C2 |
tracking |
scroll |
the section past the offset line becomes active on its link and item |
tracks the section in view and marks links and items active |
| C3 |
controlled / track=false |
scroll, prop change |
the parent owns activeId; no tracking updates are emitted |
controlled activeId and disabled tracking leave ownership to the parent |
| C4 |
scrollBehavior set |
primary click, modified |
smooth script scrolling, immediate activation, fragment update; modified clicks stay native |
scrollBehavior drives smooth navigation and updates the fragment |
| C5 |
no scrollBehavior |
click |
native fragment navigation is not intercepted |
native fragment navigation is untouched without scrollBehavior |
| C6 |
exposed root |
scrollTo, refresh |
imperative navigation and re-measurement; ariaLabelledby replaces the default label |
exposes scrollTo and refresh |
| C7 |
rendered content |
collectTocEntries |
id-bearing headings in document order with normalized text and levels |
collectTocEntries reads headings with ids in document order |
| C8 |
missing provider |
setup |
links and items fail closed with the shared context diagnostic |
compound parts require a matching root provider |
| C9 |
SSR and hydration |
isolated render/mount |
defaultActiveId renders on links and items and hydrates without warnings |
toc-ssr.test.ts |