PageToc
A linked document outline with nested section entries and standard doc-layout controls.
PageTocAn outline with real targets
The example uses a small outline in an isolated document. Each link resolves to its corresponding heading.
<PageTocPreview /> import fs from 'node:fs/promises'
import path from 'node:path'
import type { SiteConfig, TsSsgContext } from '@purestack/ts-common'
import { defineComponents } from '@purestack/ts-components'
import {
buildPageTocScript,
buildThemeSwitchScript,
} from '@purestack/ts-page-scripts'
import { renderApp } from '@purestack/ts-render'
import { getSvgIcon } from '@purestack/ts-svg-icons'
import { withBasePath } from '@purestack/ts-util'
import { defineComponent, html } from 'regor'
export interface PageTocPreview {
previewHref: string
}
const pageTocPreviewTemplate = html`<iframe
title="PageToc isolated example"
class="w-full rounded-md b-1 b-subtle"
style="height: 420px"
:src="previewHref"
></iframe>`
const exampleTemplate = html`<PageToc title="Example contents" />
<section id="preview-introduction">
<h2>Introduction</h2>
<p>A table of contents points to real section IDs.</p>
</section>
<section id="preview-details">
<h2>Details</h2>
<h3 id="preview-contract">Contract</h3>
<p>Keep headings descriptive and ordered.</p>
</section>`
function createPageTocPreviewDocument(site: SiteConfig): string {
const context: TsSsgContext = {
site: { ...site },
pageInfo: {
relPath: 'components/site/page-toc/page-toc.mdx',
urlPath: '/components/site/page-toc/',
frontmatter: {
template: 'doc',
hidden: false,
draft: false,
nav: { hidden: false },
layout: {
showNav: false,
showToc: false,
showFooter: false,
fullWidth: false,
navMode: 'sidebar',
tocCollapsed: false,
},
},
},
theme: site.style.theme,
basePath: site.basePath,
locales: site.i18n.locales,
defaultLocale: site.i18n.defaultLocale,
resolveLocaleHref: () => undefined,
resolvePublicHref: (href) => withBasePath(site.basePath, href),
recordScriptEntrypoint: () => {},
recordRuntimeEmbed: () => {},
}
context.outline = [
{ id: 'preview-introduction', title: 'Introduction', depth: 2 },
{
id: 'preview-details',
title: 'Details',
depth: 2,
children: [{ id: 'preview-contract', title: 'Contract', depth: 3 }],
},
]
const body = renderApp(exampleTemplate, {
components: defineComponents(getSvgIcon),
context,
})
const scripts =
buildThemeSwitchScript(['light', 'dark']) + buildPageTocScript()
const documentHtml = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="robots" content="noindex"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="${withBasePath(site.basePath, '/assets/site.css')}" data-theme="light"><link rel="stylesheet" href="${withBasePath(site.basePath, '/assets/site.dark.css')}" data-theme="dark"></head><body class="template-doc tone--neutral p-3" data-pagefind-ignore="all"><main class="doc-content">${body}</main><script>${scripts}</script></body></html>`
return documentHtml
}
export function definePageTocPreviewComponent(site: SiteConfig) {
return defineComponent<PageTocPreview>(pageTocPreviewTemplate, {
context: () => ({
previewHref: withBasePath(
site.basePath,
'/components/site/page-toc/preview.html',
),
}),
})
}
export async function writePageTocPreview(site: SiteConfig) {
const output = path.join(
site.outDir,
'components',
'site',
'page-toc',
'preview.html',
)
const documentHtml = createPageTocPreviewDocument(site)
await fs.mkdir(path.dirname(output), { recursive: true })
await fs.writeFile(output, documentHtml, 'utf8')
} From site config
The table of contents the doc layout renders takes its tone, variant and extra classes from the pageToc section of siteConfig.json. A page can still override the tone with layout.tocTone in its frontmatter.
{
"pageToc": {
"tone": "neutral",
"variant": "surfaceAlt",
"class": "spotlight-from-top-right"
}
} The panel keeps its own rounded corners whatever the variant, and drops its border when it collapses to a rail.
Behavior and accessibility
The default doc template already renders the page TOC; do not add a second copy to a normal guide. A sole h1 outline root is omitted so the list starts at its children. The template renders top-level entries and one nested level. Panel controls belong to the standard doc layout.
API reference
PageToc contract
Props below are the public template API. RefOrValue accepts a literal or a reactive ref; bind refs with a colon-prefixed attribute. Native attributes and events can be passed through the component root.
items
PageOutlineItem[] - Default
context.outline
PageOutlineItem[] with id, title, optional depth and children. Links are built as #id.
title
string - Default
On this page
Trimmed visible panel title. Empty text uses the default.
tone
RefOrValue<SemanticTone> - Default
inherited
Semantic intent: neutral, accent, secondary, info, success, warning, danger, feature, custom or ghost. The active skin supplies the colors.
variant
RefOrValue<ComponentVariant> - Default
flat
Visual treatment of the panel. Accepts solid, surface, surfaceAlt, spotlight, glass, flat, flatAlt, flatSolid, outlineFill, outline, subtle, subtleBtn, link, sheen, underline, rail, bracket or none. The panel keeps its own rounded corners whatever the variant.
variantMode
RefOrValue<ComponentVariantMode> - Default
stateless
Use stateless for the contents panel; stateful enables the treatment’s hover and active styles on the whole panel. Links keep their own hover and active styles either way.
Composition
No slots. Register definePageTocComponents with BtnLink and Icon. SSG context supplies the outline when items is omitted; the standard runtime handles active-section and panel behavior.