Component
Vue Scrollspy and Table of Contents
<DomScrollMenu>A smooth-scrolling table of contents that keeps real fragment links highlighted as their target headings move through a page or scroll container.
Demo
Basic
Pass each fragment href directly to DomScrollMenuItem and it renders the real anchor for you. Smooth scrolling is enabled by default.
npm install @getdom/studio<script setup>
import '@getdom/studio/style.css';
import { DomScrollMenu, DomScrollMenuItem } from '@getdom/studio';
const sections = [
{ id: 'basic-scroll-menu-overview', label: 'Overview' },
{ id: 'basic-scroll-menu-installation', label: 'Installation' },
{ id: 'basic-scroll-menu-usage', label: 'Usage' },
];
</script>
<template>
<div class="grid w-full gap-6 lg:grid-cols-[13rem_minmax(0,1fr)]">
<aside class="lg:pt-2">
<DomScrollMenu scroll-root="#basic-scroll-menu-content">
<DomScrollMenuItem
v-for="section in sections"
:key="section.id"
:href="`#${section.id}`"
>
{{ section.label }}
</DomScrollMenuItem>
</DomScrollMenu>
</aside>
<div
id="basic-scroll-menu-content"
class="h-[24rem] overflow-y-auto rounded-2xl border border-border bg-canvas px-6"
>
<section
v-for="section in sections"
:id="section.id"
:key="section.id"
class="min-h-64 scroll-mt-6 border-b border-border py-8 last:min-h-[20rem] last:border-b-0"
>
<h2 class="text-2xl font-semibold tracking-tight text-canvas-fg">
{{ section.label }}
</h2>
<p class="mt-3 max-w-xl text-sm leading-6 text-muted-fg">
Scroll the panel or select this section from the menu.
</p>
</section>
</div>
</div>
</template>
Demo
Custom link markup
Omit the href prop when you need to supply the anchor yourself. The item discovers the fragment link and exposes active state for richer content.
npm install @getdom/studio<script setup>
import '@getdom/studio/style.css';
import { ref } from 'vue';
import { DomScrollMenu, DomScrollMenuItem } from '@getdom/studio';
const activeHref = ref('');
const sections = [
{
id: 'custom-scroll-menu-overview',
label: 'Overview',
description: 'What the component watches',
depth: 0,
},
{
id: 'custom-scroll-menu-fragment-links',
label: 'Fragment links',
description: 'Native URLs and browser history',
depth: 1,
},
{
id: 'custom-scroll-menu-scroll-root',
label: 'Scroll containers',
description: 'Viewport or nested panel',
depth: 1,
},
{
id: 'custom-scroll-menu-accessibility',
label: 'Accessibility',
description: 'Current-location semantics',
depth: 0,
},
];
</script>
<template>
<div class="grid w-full gap-6 lg:grid-cols-[16rem_minmax(0,1fr)]">
<aside class="lg:pt-2">
<DomScrollMenu
v-model="activeHref"
scroll-root="#custom-scroll-menu-content"
>
<DomScrollMenuItem
v-for="section in sections"
:key="section.id"
:depth="section.depth"
>
<template #default="{ active }">
<a
:href="`#${section.id}`"
:title="`Jump to ${section.label}`"
>
<span class="flex items-start justify-between gap-3">
<span class="min-w-0">
<span class="block">{{ section.label }}</span>
<span class="mt-0.5 block text-xs font-normal text-muted-fg">
{{ section.description }}
</span>
</span>
<span
v-if="active"
class="mt-0.5 shrink-0 rounded-full bg-primary/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-primary"
>
Reading
</span>
</span>
</a>
</template>
</DomScrollMenuItem>
</DomScrollMenu>
</aside>
<div
id="custom-scroll-menu-content"
class="h-[28rem] overflow-y-auto rounded-2xl border border-border bg-canvas px-6"
>
<section
v-for="(section, index) in sections"
:id="section.id"
:key="section.id"
class="min-h-72 scroll-mt-6 border-b border-border py-8 last:min-h-[24rem] last:border-b-0"
>
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-muted-fg">
Section {{ index + 1 }}
</p>
<h2 class="mt-2 text-2xl font-semibold tracking-tight text-canvas-fg">
{{ section.label }}
</h2>
<p class="mt-3 max-w-xl text-sm leading-6 text-muted-fg">
{{ section.description }}. Scroll this panel or use the custom fragment link in the menu.
</p>
</section>
</div>
</div>
</template>
Scroll menu reference
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
modelValue | string | string | '' | Active fragment href. Use v-model when the surrounding page needs the current section. |
as | string | string | 'nav' | Root element rendered around the scroll menu. |
listAs | string | string | 'ul' | Element rendered around the menu items. |
label | string | string | 'On this page' | Accessible navigation label and default visible heading. |
showLabel | boolean | boolean | true | Show the visual navigation label above the item list. |
scrollRoot | string | object | Record<string, unknown> | — | Optional scroll-container element or CSS selector. By default the nearest scrollable ancestor of the first target is used. |
scrollBehavior | string | string | 'smooth' | Fragment navigation behavior. Smooth scrolling respects the user’s reduced-motion preference. |
rootMargin | string | string | '0px 0px -70% 0px' | IntersectionObserver root margin defining the activation band. Use a negative top value to account for sticky headers. |
Auto-generated from Scroll menu.props and inline _edit hints.
Events
| Name | Payload | Description |
|---|---|---|
| @update:modelValue | href | Emitted when scrolling or navigation changes the active fragment link. |
| @change | { href, target } | Emitted with the active fragment and its target element. |
Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.
Slots
| Name | Scope | Description |
|---|---|---|
| #(default) | { activeHref } | DomScrollMenuItem rows or equivalent custom content. |
| #label | { label } | Custom visible navigation label. |
Scroll menu item reference
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
href | string | string | '' | Optional fragment href shorthand, including the leading #. Omit it when the slot provides its own anchor. |
as | string | string | 'li' | Element wrapping the anchor. Use li with the default DomScrollMenu list. |
depth | number | number | 0 | Visual nesting depth for subordinate page headings. |
disabled | boolean | boolean | false | Remove the fragment link from navigation and scrollspy selection. |
Auto-generated from Scroll menu item.props and inline _edit hints.
Events
| Name | Payload | Description |
|---|---|---|
| @navigate | { href, active } | Emitted when the fragment link is activated with an unmodified primary click. |
Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.
Slots
| Name | Scope | Description |
|---|---|---|
| #(default) | { active, href } | Link contents when href is set, or one custom fragment anchor when href is omitted. |