Component
Scroll menu
<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.
<script setup>
import { DomScrollMenu, DomScrollMenuItem } from '@getdom/studio/vue';
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.
<script setup>
import { ref } from 'vue';
import { DomScrollMenu, DomScrollMenuItem } from '@getdom/studio/vue';
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. |