TableOfContents
TableOfContents builds a navigable outline from page headings.
Source
LLM docs
Package
Examples
5
Properties
42
Playground
Basics
Drop a table of contents beside your main article and it will register headings automatically through the shared title registry.
Loading demo…
import { Block, Row, TableOfContents, Text, Title, TitleRegistryProvider } from '@plocks/ui';
const SECTIONS = [
{ id: 'intro', title: 'Introduction', summary: 'Set the stage for the walkthrough.' },
{ id: 'setup', title: 'Setup', summary: 'Install dependencies and initialize the provider.' },
{ id: 'usage', title: 'Usage', summary: 'Render headings inside your content area to register them.' },
{ id: 'faq', title: 'FAQ', summary: 'Answer the questions you expect most often.' },
];
export function Demo() {
return (
<TitleRegistryProvider>
<Row gap="xl" align="flex-start">
<TableOfContents
container="#toc-basic-content"
variant="outline"
size="sm"
p="sm"
style={{ width: 240 }}
/>
<Block id="toc-basic-content" grow={1} style={{ maxWidth: 560 }}>
{SECTIONS.map((section, index) => (
<Block key={section.id}>
<Title order={index === 0 ? 1 : 2}>{section.title}</Title>
<Text c="secondary">{section.summary}</Text>
</Block>
))}
</Block>
</Row>
</TitleRegistryProvider>
);
}Variants
Choose between the
outline, ghost, filled, and none variants. Pair filled with autoContrast to keep labels legible against a brand color.Loading demo…
import { Row, TableOfContents } from '@plocks/ui';
const ITEMS = [
{ id: 'overview', value: 'Overview', depth: 1 },
{ id: 'tokens', value: 'Color tokens', depth: 2 },
{ id: 'accessibility', value: 'Accessibility', depth: 1 },
];
export function Demo() {
return (
<Row gap="md" align="flex-start" wrap="wrap">
<TableOfContents initialData={ITEMS} variant="outline" size="xs" style={{ width: 200 }} />
<TableOfContents initialData={ITEMS} variant="ghost" size="xs" style={{ width: 200 }} />
<TableOfContents
initialData={ITEMS}
variant="filled"
color="primary.6"
autoContrast
size="xs"
style={{ width: 200 }}
/>
</Row>
);
}Preloaded data
Seed the table of contents with
initialData so servers and prerender jobs can render the navigation before headings mount.Loading demo…
import { Block, TableOfContents } from '@plocks/ui';
const INITIAL_ITEMS = [
{ id: 'overview', value: 'Overview', depth: 1 },
{ id: 'setup', value: 'Setup', depth: 2 },
{ id: 'usage', value: 'Usage', depth: 2 },
{ id: 'advanced', value: 'Advanced', depth: 1 },
{ id: 'faq', value: 'FAQ', depth: 1 },
];
export function Demo() {
return (
<Block align="flex-start">
<TableOfContents
initialData={INITIAL_ITEMS}
variant="outline"
depthOffset={16}
radius="sm"
size="sm"
p="sm"
style={{ width: 240 }}
/>
</Block>
);
}Depth offset
Use
minDepthToOffset and depthOffset to indent nested headings so deep sections are easy to scan.Loading demo…
import { Block, TableOfContents } from '@plocks/ui';
const INITIAL_ITEMS = [
{ id: 'intro', value: 'Introduction', depth: 1 },
{ id: 'schedule', value: 'Release schedule', depth: 2 },
{ id: 'api', value: 'API reference', depth: 2 },
{ id: 'hooks', value: 'Hooks', depth: 3 },
{ id: 'migration', value: 'Migration', depth: 1 },
];
export function Demo() {
return (
<Block align="flex-start">
<TableOfContents
initialData={INITIAL_ITEMS}
variant="outline"
minDepthToOffset={2}
depthOffset={28}
size="xs"
p="sm"
style={{ width: 240 }}
/>
</Block>
);
}Active callbacks
Subscribe to
onActiveChange to surface the currently highlighted section, perfect for syncing status chips or analytics.Loading demo…
import { useState } from 'react';
import { Block, Chip, Row, TableOfContents, Text, Title, TitleRegistryProvider } from '@plocks/ui';
const SECTIONS = [
{ id: 'overview', title: 'Overview', summary: 'Explain when the progress indicator should appear.' },
{ id: 'loading', title: 'Loading States', summary: 'Describe feedback while content is fetching.' },
{ id: 'error', title: 'Error Recovery', summary: 'Clarify what happens if the data fails to load.' },
];
export function Demo() {
const [activeId, setActiveId] = useState<string | null>(null);
return (
<TitleRegistryProvider>
<Block>
<Chip variant="light" color={activeId ? 'primary' : 'gray'} size="sm">
Active section: {activeId ?? 'None'}
</Chip>
<Row gap="xl" align="flex-start">
<TableOfContents
container="#toc-active-callback-content"
variant="outline"
size="xs"
p="sm"
style={{ width: 240 }}
onActiveChange={setActiveId}
/>
<Block id="toc-active-callback-content" grow={1} style={{ maxWidth: 560 }}>
{SECTIONS.map((section, index) => (
<Block key={section.id}>
<Title order={index === 0 ? 1 : 2}>{section.title}</Title>
<Text c="secondary">{section.summary}</Text>
</Block>
))}
</Block>
</Row>
</Block>
</TitleRegistryProvider>
);
}React Native design for iOS, Android, and Web.
Quick Links
Resources