Skip to content

VirtualScrollbar

A themable custom scrollbar overlay. Decoupled from useVirtualScroll — it works with the scroll element exposed by any component above (via getScrollElement()), or any scrollable element you pass directly. Renders a track + draggable thumb sized and positioned from scrollHeight/clientHeight/scrollTop, and drags with pointer events.

The thumb is a full role="scrollbar" widget: aria-orientation/aria-valuemin/aria-valuemax/aria-valuenow reflect the current scroll position, and it's keyboard-operable — Arrow keys step, Home/End jump to the start/end, PageUp/PageDown jump by a viewport's worth.

Props

target

() => HTMLElement | null

Returns the scroll element to sync with.

orientation

'vertical' | 'horizontal' · default: 'vertical'

Scroll axis to track.

minThumbSize

number · default: 24

Minimum thumb size in px, so a huge list doesn't shrink it to an ungrabbable sliver.

Exposed API

refresh()

Force-reattach and recompute against the current target() — useful after a manual DOM swap that the component's own ResizeObserver/scroll listeners wouldn't otherwise pick up.

ts
scrollbarRef.value?.refresh()

CSS custom properties

--vvsk-scrollbar-size

Default: 10px

Thickness of the track/thumb.

--vvsk-scrollbar-track

Default: transparent

Track background.

--vvsk-scrollbar-thumb

Default: rgb(255 255 255 / 25%)

Thumb background.

--vvsk-scrollbar-thumb-hover

Default: rgb(255 255 255 / 40%)

Thumb background while hovered/dragged.

Example

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VirtualList, VirtualScrollbar } from 'vue-virtual-scroller-kit'
import type { VirtualListExpose } from 'vue-virtual-scroller-kit'

const listRef = ref<VirtualListExpose | null>(null)
const items = Array.from({ length: 10_000 }, (_, i) => ({ id: i, text: `Row ${i + 1}` }))
</script>

<template>
  <div style="position: relative; display: flex; height: 500px">
    <VirtualList
      ref="listRef"
      :items="items"
      :estimated-item-size="48"
      class="vvsk-scrollbar-hidden"
      style="flex: 1"
    >
      <template #default="{ item }">
        <div style="padding: 12px 16px; border-bottom: 1px solid #eee">{{ item.text }}</div>
      </template>
    </VirtualList>

    <VirtualScrollbar :target="() => listRef?.getScrollElement() ?? null" />
  </div>
</template>

Hide the native scrollbar on the paired container when using VirtualScrollbar (optional — the two can coexist if you want both):

css
.vvsk-scrollbar-hidden {
  scrollbar-width: none; /* Firefox */
}
.vvsk-scrollbar-hidden::-webkit-scrollbar {
  display: none; /* Chrome, Safari, Edge */
}