TableOfContents
The contents of a page as links to its headings, with the section in view marked; as a list beside the content or behind a button above it.
Table lists the headings of a page as links to their anchors and marks the section the
reader is in. Beside the content it is a list; on a narrow screen or at a high zoom it is the same
list behind a button, above the content.
Keys
A section of the page. Scroll past it, and the list marks the section you are in.
Plurals
A section of the page. Scroll past it, and the list marks the section you are in.
Zero and one
A section of the page. Scroll past it, and the list marks the section you are in.
Missing keys
A section of the page. Scroll past it, and the list marks the section you are in.
// apps/docs/examples/ui/table-of-contents.tsx'use client';
import {TableOfContents} from '@workspace/active-ui/components/composed/table-of-contents';
const ITEMS = [ {id: 'toc-example-keys', text: 'Keys', depth: 2}, {id: 'toc-example-plurals', text: 'Plurals', depth: 2}, {id: 'toc-example-zero', text: 'Zero and one', depth: 3}, {id: 'toc-example-missing', text: 'Missing keys', depth: 2},];
/** * The contents of a page in its two forms: the list beside the content, and * the collapsible form above it on a narrow screen. The section in view is * marked as the reader scrolls past the headings of this example. */export default function TableOfContentsExample() { return ( <div className="grid w-full gap-8 sm:grid-cols-[1fr_14rem]"> <div className="flex flex-col gap-6 text-sm"> {ITEMS.map((item) => ( <section key={item.id} className="flex flex-col gap-1"> {item.depth === 2 ? ( <h3 id={item.id} className="font-medium"> {item.text} </h3> ) : ( <h4 id={item.id} className="font-medium"> {item.text} </h4> )} <p className="text-muted-foreground"> A section of the page. Scroll past it, and the list marks the section you are in. </p> </section> ))} <TableOfContents items={ITEMS} label="On this page" variant="collapsible" className="border-border border-y" /> </div> <TableOfContents items={ITEMS} label="Contents" /> </div> );}What the composition adds#
The section in view, marked with
aria-: the last heading abovecurrent="location" offsetpixels from the top of the window (the height of a sticky header), or the first heading before the reader reaches any. A heading that is not in the document is skipped.Any scroll area: it follows the scrolling of the window and of any element the page scrolls in, such as the content area of an application shell below a fixed header. There is nothing to configure.
The mark stays in sight: when you give the list a maximum height so it scrolls on its own (a sticky column in a low window), the marked entry is kept in view inside the list; the page itself does not move.
A mark without a heavier font: colour and a short bar beside the marked entry only, so the list never shifts. There is no line along the list.
Two forms:
variant="list"(the default) andvariant="collapsible", closed at first, built on Collapsible.Depth: the top level of the items is flush, deeper levels are indented.
Usage#
import {TableOfContents} from '@workspace/active-ui/components/composed/table-of-contents';
<TableOfContents items={[ {id: 'keys', text: 'Keys', depth: 2}, {id: 'plurals', text: 'Plurals', depth: 2}, ]} label="On this page"/>;What your application provides#
The headings, with the anchors the page renders: for documentation, the table of contents of the page (
Pageof active-docs).View. toc The label, translated: it names the navigation and is the button of the collapsible form.
Where each form goes: the list in a column beside the content, the collapsible form above it.
Accessibility and keyboard#
A <nav> named by label. Every entry is a link, reached with Tab; the collapsible form opens
with Enter or Space on its button.
API#
Import from @workspace/
Props
Other props, such as the standard attributes of the underlying element, className and ref, are passed through.
Types
"On this page": the headings of a page as links to their anchors, with the section the reader is in marked (aria-). The mark is a colour and a border, never a heavier font, so the list does not shift.
variant="list"(the default): a labelled list, for a column beside the content.variant="collapsible": the same list behind a button with the label, closed at first, for a narrow screen or zoom, above the content.
The section in view is the last heading above offset pixels from the top of the viewport (a sticky header's height), or the first heading before the reader reaches any. Headings that are not in the document are skipped. It follows every scroll: of the window, and of any element the page scrolls in (an application shell whose content area scrolls below a fixed header), with no option to set.
In a list taller than its room, the caller makes the list (or the whole navigation) scroll, for example with a maximum height; the marked entry then stays in view inside that scroll area, and nothing outside it moves. The label is the caller's: the component translates nothing.
interface TableOfContentsItem {
id: string;
text: string;
depth: number;
}The props of Table, each described in its prop list on this page.
interface TableOfContentsProps extends Omit<
React.ComponentProps<'nav'>,
'children'
> {
items: readonly TableOfContentsItem[];
label: string;
variant?: 'list' | 'collapsible';
offset?: number;
}