Table of Contents
The “on this page” rail, with a marker that slides onto the section you are reading.
Walkthrough
1. Basic
The rail follows whatever scrolls — here a panel, on this page the window. Scroll the column on the left and the marker slides onto the section whose heading has gone past the top.
<script lang="ts"> import { Toc } from 'omaris'; const sections = [ { id: 'demo-install', text: 'Installation', level: 2 }, { id: 'demo-cli', text: 'With the CLI', level: 3 }, { id: 'demo-manual', text: 'By hand', level: 3 }, { id: 'demo-theming', text: 'Theming', level: 2 }, { id: 'demo-tokens', text: 'Design tokens', level: 3 }, { id: 'demo-recipes', text: 'Recipes', level: 2 } ];</script><!-- One column on a phone: a 12rem rail beside the article leaves the article about 60px, which is one word a line.--><div class="grid w-full grid-cols-1 gap-8 sm:grid-cols-[minmax(0,1fr)_12rem]"> <!-- svelte-ignore a11y_no_noninteractive_tabindex --> <div class="scrollbar-subtle h-80 overflow-y-auto rounded-shape-md border border-border" tabindex="0" role="group" aria-label="Article" > <div class="flex flex-col gap-6 p-5"> {#each sections as section (section.id)} <section class="flex flex-col gap-2"> <h2 id={section.id} class="scroll-mt-5 text-title-sm text-foreground">{section.text}</h2> <p class="text-body-md text-muted-foreground"> Enough copy to scroll past, so the rail beside it has something to follow. The marker measures the link it lands on, so it fits a wrapped label as readily as a short one. </p> </section> {/each} </div> </div> <!-- `offset` is measured from the top of whatever scrolls — here the panel. --> <Toc items={sections} offset={24} /></div> 2. Levels
Indentation is measured from the shallowest heading in the list, so a page that never uses h2 reads flush instead of indented once.
h2 · Overview
h3 · Props
h4 · The class prop
h4 · The classes prop
h2 · Parts
<script lang="ts"> import { Toc } from 'omaris'; const items = [ { id: 'lv-overview', text: 'Overview', level: 2 }, { id: 'lv-props', text: 'Props', level: 3 }, { id: 'lv-props-class', text: 'The class prop', level: 4 }, { id: 'lv-props-classes', text: 'The classes prop', level: 4 }, { id: 'lv-parts', text: 'Parts', level: 2 } ]; /** The page beside the rail, indented the same way the rail is. */ const indent = { 2: '', 3: 'ps-5', 4: 'ps-10' } as const;</script><!-- One column on a phone; the rail is 12rem and would leave nothing beside it. --><div class="grid w-full grid-cols-1 items-start gap-8 sm:grid-cols-[minmax(0,1fr)_12rem]"> <div class="flex flex-col gap-3 rounded-shape-md border border-border p-5"> {#each items as item (item.id)} <p id={item.id} class="text-body-md text-foreground {indent[item.level as 2 | 3 | 4]}"> h{item.level} · {item.text} </p> {/each} </div> <Toc {items} spy={false} active="lv-props-classes" label="Mixed levels" /></div> 3. Controlled
spy={false} hands the highlight over: bind:active is then the only thing that moves the marker.
active: ct-usage
<script lang="ts"> import { Toc, Button } from 'omaris'; const items = [ { id: 'ct-overview', text: 'Overview', level: 2 }, { id: 'ct-usage', text: 'Usage', level: 2 }, { id: 'ct-api', text: 'API', level: 2 } ]; let active = $state('ct-usage');</script><!-- One column on a phone; the rail is 11rem and would leave nothing beside it. --><div class="grid w-full grid-cols-1 items-start gap-8 sm:grid-cols-[minmax(0,1fr)_11rem]"> <div class="flex flex-col items-start gap-4"> <div class="flex flex-wrap gap-2"> {#each items as item (item.id)} <Button id={item.id} size="sm" variant={active === item.id ? 'filled' : 'outlined'} onclick={() => (active = item.id)} > {item.text} </Button> {/each} </div> <p class="text-body-sm text-muted-foreground">active: {active}</p> </div> <Toc {items} spy={false} bind:active /></div> 4. Overridden
Every part is reachable: classes reaches the rail, the marker and the links, so the same component can be a dashed outline with a thick marker and no heading at all.
Summary
Detail
Notes
<script lang="ts"> import { Toc } from 'omaris'; const items = [ { id: 'ov-summary', text: 'Summary', level: 2 }, { id: 'ov-detail', text: 'Detail', level: 3 }, { id: 'ov-notes', text: 'Notes', level: 3 } ];</script><div class="flex items-start gap-10"> <Toc {items} spy={false} active="ov-detail" showLabel={false} size="sm" class="w-44 max-w-full" classes={{ rail: 'border-dashed border-primary/40', marker: 'w-1 rounded-none', link: 'py-2 tracking-wide uppercase', item: 'border-b border-border/60 last:border-b-0' }} /> <div class="flex flex-col gap-2"> {#each items as item (item.id)} <p id={item.id} class="text-body-sm text-muted-foreground">{item.text}</p> {/each} </div></div>