GroupedVirtualList
Renders items grouped under collapsible section headers. Each group can be expanded or collapsed with a smooth CSS animation.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
groups | GroupDef<T>[] | — | Array of group definitions |
estimatedItemSize | number | 50 | Estimated height of item rows |
estimatedGroupHeaderSize | number | 40 | Estimated height of group header rows |
overscan | number | 3 | Extra rows rendered outside viewport |
keyField | string | 'id' | Field used as the item key |
motionBlur | boolean | false | Apply a CSS blur that scales with scroll velocity while scrolling fast |
stickyGroupHeaders | boolean | false | Keep the current group's header pinned to the top as its items scroll underneath (overlay, see below) |
stickyGroupHeadersrenders as a persistent overlay above the list, reusing the#group-headerslot for whichever group is currently at the top of the viewport — not a real CSSposition: stickyrow, which can't work here because virtualized rows areposition: absolute. It snaps instantly to the next group (no "push" animation).
GroupDef<T>
ts
interface GroupDef<T> {
key: string // unique identifier for the group
label: string // display label
items: T[] // items in this group
collapsed?: boolean // initial collapsed state
}Slots
| Slot | Scope | Description |
|---|---|---|
#group-header | { group: GroupDef<T>, toggle: () => void, isCollapsed: boolean } | Custom group header |
#default | { item: T, index: number, groupKey: string } | Item row content |
#empty | — | Shown when all groups are empty |
Emits
Same as VirtualList: scroll, visible-range-change.
Exposed API (GroupedVirtualListExpose)
ts
import type { GroupedVirtualListExpose } from 'vue-virtual-scroller-kit'
const listRef = ref<GroupedVirtualListExpose | null>(null)
listRef.value?.toggle('group-key') // toggle a group open/closed
listRef.value?.scrollTo(index) // scroll to a flat row index
listRef.value?.scrollTo(index, 'start', { behavior: 'smooth' })
listRef.value?.getScrollElement() // pair with VirtualScrollbarExample
vue
<script setup lang="ts">
import { ref } from 'vue'
import { GroupedVirtualList } from 'vue-virtual-scroller-kit'
import type { GroupDef, GroupedVirtualListExpose } from 'vue-virtual-scroller-kit'
interface Contact {
id: number
name: string
email: string
}
const groups = ref<GroupDef<Contact>[]>([
{
key: 'a',
label: 'A',
items: [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Aaron', email: 'aaron@example.com' },
],
},
{
key: 'b',
label: 'B',
items: [{ id: 3, name: 'Bob', email: 'bob@example.com' }],
collapsed: true,
},
])
const listRef = ref<GroupedVirtualListExpose | null>(null)
function expandAll() {
groups.value = groups.value.map((g) => ({ ...g, collapsed: false }))
}
function collapseAll() {
groups.value = groups.value.map((g) => ({ ...g, collapsed: true }))
}
</script>
<template>
<button @click="expandAll">Expand all</button>
<button @click="collapseAll">Collapse all</button>
<GroupedVirtualList
ref="listRef"
:groups="groups"
:estimated-item-size="56"
style="height: 500px"
>
<template #group-header="{ group, toggle, isCollapsed }">
<div class="group-header" @click="toggle">
{{ isCollapsed ? '▶' : '▼' }} {{ group.label }}
<span>({{ group.items.length }})</span>
</div>
</template>
<template #default="{ item }">
<div class="contact-row">
<strong>{{ item.name }}</strong>
<span>{{ item.email }}</span>
</div>
</template>
</GroupedVirtualList>
</template>