PageToc

A linked document outline with nested section entries and standard doc-layout controls.

PageToc

View source · API reference

An 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.