vue-use-active-scroll
raw JSON → 1.1.3 verified Sat Apr 25 auth: no javascript
Vue 3 composable for accurate TOC/sidebar active link tracking without Intersection Observer compromises. Current stable version 1.1.3 (released 2024). Zero runtime dependencies, ships TypeScript types. Unlike typical Intersection Observer solutions, it implements a custom scroll observer that reliably highlights clicked links that never intersect, handles first/last link at page boundaries, and works consistently across scroll speeds, smooth scrolling, and hash navigation. Supports template refs or DOM elements in place of IDs, customizable offsets per scroll direction, and custom scroll containers. Does not scroll to targets or manipulate DOM. Released on npm with monthly cadence.
Common errors
error Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'hash') ↓
cause Vue Router scrollBehavior not configured or to.hash undefined when clicking a hash link.
fix
Add scrollBehavior(to) { if (to.hash) return { el: to.hash } } to createRouter({...}).
error useActiveScroll is not a function ↓
cause Using a default import instead of named import.
fix
Use import { useActiveScroll } from 'vue-use-active-scroll' instead of import useActiveScroll from 'vue-use-active-scroll'.
error Cannot find module 'vue-use-active-scroll' or its corresponding type declarations. ↓
cause Package not installed or TypeScript cannot resolve types.
fix
Run npm install vue-use-active-scroll (or pnpm/yarn/bun add) and ensure tsconfig includes node_modules/@types.
Warnings
gotcha Vue Router is required for hash-based scrolling unless using Nuxt. Without configuring scrollBehavior in createRouter({...}), clicking hash links may not scroll to the target element. ↓
fix Add scrollBehavior(to) { if (to.hash) return { el: to.hash } } to your Vue Router instance.
gotcha CSS scroll-behavior must be set on html element (e.g., scroll-behavior: smooth) for smooth scrolling. If omitted, scrolling jumps instantly and active highlight may feel abrupt. ↓
fix Add 'html { scroll-behavior: smooth; }' in your global CSS.
breaking Version 1.1.0 changed target API: targets can now be set using template refs or DOM elements in place of IDs. If you previously relied on string IDs only, this is backward-compatible, but passing refs now works differently. ↓
fix No action needed if using string IDs. If using refs, ensure they are valid TemplateRef or HTMLElement.
gotcha The activeId returned is a Ref<string | null>, not a plain string. In templates it auto-unwraps, but in script you must use .value. ↓
fix Access activeId.value in script setup or composition functions.
deprecated The export 'isActive' is deprecated since v1.1.1? Actually 'isActive' is still exported but also available via useActiveScroll return. There is no deprecation notice, but note that using isActive alone might not work in SSR (fixed in 1.1.1). ↓
fix Import useActiveScroll and destructure isActive from its return, or upgrade to >=1.1.1.
Install
npm install vue-use-active-scroll yarn add vue-use-active-scroll pnpm add vue-use-active-scroll Imports
- useActiveScroll wrong
const useActiveScroll = require('vue-use-active-scroll')correctimport { useActiveScroll } from 'vue-use-active-scroll' - useActiveScroll wrong
import useActiveScroll from 'vue-use-active-scroll'correctimport { useActiveScroll } from 'vue-use-active-scroll' - isActive wrong
import { IsActive } from 'vue-use-active-scroll'correctimport { isActive } from 'vue-use-active-scroll'
Quickstart
<template>
<nav>
<a
v-for="item in items"
:key="item.id"
:href="'#' + item.id"
:class="{ active: activeId === item.id }"
>
{{ item.title }}
</a>
</nav>
<main>
<section v-for="item in items" :key="item.id" :id="item.id">
<h2>{{ item.title }}</h2>
<p>{{ item.content }}</p>
</section>
</main>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useActiveScroll } from 'vue-use-active-scroll'
const items = [
{ id: 'introduction', title: 'Introduction', content: '...' },
{ id: 'installation', title: 'Installation', content: '...' },
{ id: 'usage', title: 'Usage', content: '...' },
]
// Array of target IDs (strings, no hash prefix)
const targetIds = items.map((item) => item.id)
const { activeId } = useActiveScroll(targetIds, {
offset: { top: 80, bottom: 80 },
firstTopMargin: 100,
lastBottomMargin: 100,
})
</script>