On-Page TOC
The canonical wiring for an in-page TOC alongside long-form content — articles, docs pages, change logs, anything with five or more headings. WireKit ships two primitives that map onto the two common shapes:
- Sidebar TOC (
<x-wirekit::reading-spine>) — right-edge sticky vertical sidebar, hover-to-expand, narrow at rest. Matches Stripe, Linear, Vercel, shadcn docs. Recommended for docs / articles. - Horizontal strip TOC (
<x-wirekit::reading-toc>) — sticky strip across the top of the viewport. Right shape for flat marketing pages with 3-4 anchored sections. See the marketing-landing-toc recipe.
This recipe covers the sidebar pattern.
The canonical wiring
Three primitives compose:
<x-wirekit::reading-progress>— the top-edge progress bar (full-page, not scoped to article). Optional; drop if your layout doesn't want one.<x-wirekit::reading-spine>— the right-edge sidebar TOC. Reads<main>headings at the H2 / H3 levels.expand="hover"keeps the spine narrow at rest; expands to show labels on hover.<main>content — the spine reads headings from this element. Usetarget="main"(the prop defaultnullresolves to'main, article'first-match wins).
CSS variables
The reading-spine reads these CSS variables; override in the wrapping element's style="…" or in :root {} for a global preset:
| Variable | Default | Purpose |
|---|---|---|
--reading-spine-width-collapsed |
0.5rem |
Narrow-at-rest width (visible as a tick column) |
--reading-spine-width-expanded |
14rem |
Expanded width on hover / expand="always" |
--reading-spine-offset-top |
1rem |
boundary="container" only — pins the spine below the scroll-container top. Not used in this viewport-pinned recipe (the spine is vertically centered). |
--reading-spine-padding-y |
2rem |
Vertical padding inside the spine column |
--color-wk-reading-spine-active |
var(--color-wk-accent) |
Active-link color |
For the full token reference and theming guide, see /theming#reading-spine.
Integration with a sticky header
If your layout has a sticky header (e.g. <x-wirekit::brand-bar sticky>), pass the bar's height to the spine's :offset prop. The viewport-pinned spine is vertically centered, so it never sits behind the bar — but the IntersectionObserver fires when a heading crosses the viewport top (behind the bar), so without the offset the active link activates late. :offset moves that detection line down by the bar's height.
Composing with a breadcrumb
If your layout also carries a <x-wirekit::breadcrumb>, the breadcrumb's active node and the spine's active link should reference the same logical position — both reflect "where the reader is". The breadcrumb is page-level (which page in the docs tree?); the spine is in-page (which section on the current page?).
The breadcrumb stays static for the whole page; the spine's active link updates as the reader scrolls.
Tips
- Use
expand="always"on dedicated docs layouts (where readers expect the TOC) instead of the defaultexpand="hover"— saves a hover-discovery moment. - Mobile users see no spine by default. The narrow-viewport experience drops the TOC; readers scroll the article without it. The progress bar at the top still works on mobile.
- For flat content (3-4 sections, all
<h2>), prefer the horizontal<x-wirekit::reading-toc>strip — see the marketing-landing-toc recipe. - For very long articles (50+ headings), the spine works but readers benefit from the
<x-wirekit::reading-minimap>variant — a Reflexion of the article's structure as a vertical ruler.
See also
<x-wirekit::reading-spine>— the sidebar primitive in detail.<x-wirekit::reading-progress>— the top-edge progress bar.<x-wirekit::reading-shell>— the one-tag composition wrapper that bundles progress + spine + bookmark with sensible defaults.- marketing-landing-toc recipe — the horizontal strip variant for flat marketing pages.
- long-form-article recipe — the full long-form article composition (max-width container + reading-meta + prose body).
- documentation-reader recipe — the docs-style composition (reading-progress + app-shell + reading-spine + article).