Scroller

Virtualized list for efficiently rendering large datasets.

Usage

You have a long list and need it to stay fast. Think of it like lazy loading for list items: instead of loading every item on the page at once, it will render only those currently visible. Scroll down, old items are removed from the DOM, new ones take their place. A list of 10,000 items performs the same as a list of 10.

If your list is short or already renders quickly without it, you don't need this component.

  • Fixed height (default): All items must be the same height. Provide :item-size in pixels. Scroll position is calculated with simple math — best performance for uniform lists (contacts, search results, menu items).

  • Variable height (dynamic): Items can grow to fit their content. Provide :min-item-size as an initial estimate; the component measures each item with ResizeObserver after render and adjusts accordingly. Use for content-driven layouts like chat threads or feeds.

The rule of thumb: if every item in your list is the same height, use fixed. If heights depend on the content inside, use dynamic.

Variants

Fixed height items

Use when all items share a known, uniform height. Set :item-size to that height in pixels.

Variable height items

Use when item heights depend on their content. Set dynamic="true" and :min-item-size to the smallest expected item height — the component measures actual sizes after render.

Direction

Defaults to vertical. Set to horizontal for a horizontal scroller.

Infinite Scroll

Use the after slot for a loading indicator that scrolls with the list, and scroll-end to fetch the next page. scroll-end fires when the last item enters the rendered view pool — at least one buffer length before the viewport's end edge, earlier still if before/after slot content adds to the window — so the fetch starts before the user runs out of content.

scroll-start is the mirror image, for prepending older items ahead of the viewport's start edge.

The scroller recomputes its rendered window on mount and on scroll. It does not watch items, so after you append a page call updateItems() on the component ref — otherwise the new rows will not appear until the next scroll.

Assign a new array rather than mutating the existing one in place. The scroller itself does not mind either way, but watching items by reference is the usual way a wrapper component triggers the updateItems() call for you, and a reference watcher never sees an in-place push.

<dt-scroller
 ref="scroller"
 :items="items"
 :item-size="32"
 :buffer="400"
 :scroller-height="200"
 @scroll-end="fetchNextPage"
 >
 <template #default="{ item }">
   <div class="user">
     {{ item.name }}
   </div>
 </template>
 <template #after>
   <dt-loader v-if="isFetching" />
 </template>
</dt-scroller>
async function fetchNextPage () {
  if (isFetching.value) return;
  isFetching.value = true;
  try {
    const page = await fetchMore();
    items.value = [...items.value, ...page];
    await nextTick();
    scroller.value.updateItems();
  } finally {
    isFetching.value = false;
  }
}

The empty slot only renders while the list is empty.

Vue API

import { DtScroller } from '@dialpad/dialtone-vue';

Slots

Name
Type
after

Content rendered after the items, inside the scroll viewport. Scrolls with the list.

before

Content rendered before the items, inside the scroll viewport. Scrolls with the list.

default
empty

Content rendered inside the list wrapper only while the list is empty.

Props

Name
Default
Type
items required
array

The items to render. If the items are simple arrays, the index will be used as the key. If the items are objects, the keyField will be used as the key.

buffer
200
number

Amount of pixels added to each edge of the scroll viewport, so items are rendered before they scroll into view. Also controls how early scroll-start and scroll-end fire relative to the viewport edges.

direction
'vertical'
"vertical" | "horizontal"

The direction of the scroller.

dynamic
false
"true" | "false"

Indicates if the items need to react to changes in their size. If disabled the itemSize prop is required and you will get improved performance. If enabled the minItemSize prop is required and you will have reduced performance but the ability to reactively size list items

itemSize
null
number

Display height (or width in horizontal mode) of the items in pixels used to calculate the scroll size and position. Required if DYNAMIC is false

itemTag
'div'
string

The tag to use for the items.

keyField
'id'
string

The key field to use for the items. If the items are objects, the scroller needs to be able to identify them. By default it will look for an id field on the items. This can be configured with this prop if you are using another field name.

listTag
'div'
string

The tag to use for the list.

minItemSize
null
number|string

Minimum size used if the height (or width in horizontal mode) of a item is unknown. Is required for the initial render of items in DYNAMIC size mode.

scrollerHeight
'100%'
string|number

The height of the scroller. Can be a number (in pixels) or a string (in CSS units).

scrollerWidth
'100%'
string|number

The width of the scroller. Can be a number (in pixels) or a string (in CSS units).

Events

Name
Type
scroll-end

Emitted when the last item enters the rendered view pool. Fires at least one buffer length before the viewport's end edge is reached (earlier still with after slot content), so callers can append items ahead of time.

scroll-start

Emitted when the first item enters the rendered view pool. Fires at least one buffer length before the viewport's start edge is reached (earlier still with before slot content), so callers can prepend items ahead of time.

user-position

Describe when the scroller changes from block-start (aka top) / middle / block-end (aka bottom). The top and bottom values are deprecated and will ideally be removed in the next major version. Please use blockStart and blockEnd instead.

Scroller documentation last updated Friday, September 4, 2026

fix/popover-modal-zindex-scope