Manual UI Templates VirtualList

VirtualList

elements man ui/templates/virtual-list

A long list, or a grid of rows and columns, that renders only the items near the viewport, with measured row heights.

Example
import { VirtualList, VirtualItem, VirtualCell, initialRange } from "@elements/ui/virtual-list"; <!-- private state = { range: initialRange(people.length, 20) } --> <VirtualList count={people.length} range={state.range} estimateSize={48} label="People" class="people-list"> <VirtualItem e:for={i of state.range} e:key={(i) => people[i].id} index={i}> {people[i].name} </VirtualItem> </VirtualList> <!-- the page scrolls instead of the list --> <VirtualList scroller="window" count={rows.length} range={state.range}>...</VirtualList> <!-- a grid: rows and columns both window in one scroller --> <VirtualList role="grid" count={10000} columns={1000} range={state.rows} columnRange={state.cols} estimateSize={36} estimateColumnSize={120}> <VirtualItem e:for={r of state.rows} e:key={(r) => r} index={r}> <VirtualCell e:for={c of state.cols} e:key={(c) => c} column={c}>{r}, {c}</VirtualCell> </VirtualItem> </VirtualList>

Parts

Part What it is
VirtualList The scroll container. Owns the virtualizer and writes range, the indexes to render.
VirtualItem One rendered item. Takes its index; the list measures it and places it at its offset.
VirtualCell One cell of a grid row (columns > 0). Takes its column; the list places it at the column's offset and width.
.virtual-list-inner The track inside the scroller, as long as every item together.

Attributes

VirtualList

Attribute Type Default Description
count number (required) How many items the list has. Required.
range number[] (two-way) [] The indexes to render, written by the list as it scrolls. Loop over it with <VirtualItem index>. Seed it with initialRange(count, n) so the server renders the first n items.
estimateSize number | ((index: number) => number) 40 Item size in px before it is measured. Sizes the scrollbar until items render.
overscan number 5 Extra items rendered before and after the viewport. With lanes, rows of items.
orientation "vertical" | "horizontal" "vertical" Scroll axis.
gap number 0 Space between items in px.
lanes number 1 Columns (vertical) or rows (horizontal): a masonry layout; each item goes in the shortest lane.
paddingStart number 0 Space before the first item in px.
paddingEnd number 0 Space after the last item in px.
scrollPaddingStart number 0 Offset kept clear when scrolling an item into view at the start (a sticky header's height).
scrollPaddingEnd number 0 Offset kept clear at the end.
initialOffset number 0 Scroll position in px when the list starts.
scroller "self" | "window" "self" self: the list scrolls (give it a height). window: the page scrolls and the list is as tall as its items.
anchor "start" | "end" "start" The side that holds still when items change. end suits chat and logs.
followOnAppend boolean | "auto" | "smooth" false With anchor end: stay at the end when items are added while already there; "smooth" glides to the new item.
measure boolean true Measure each item's real size. false uses estimateSize as the exact size (fixed rows, no measuring).
rangeExtractor ((range) => number[]) | null null Which indexes render for the visible range. defaultRangeExtractor is exported to build on.
sticky number[] [] Indexes of header items. The last one above the viewport stays rendered and pinned to the top.
itemKey (index: number) => string | number (i) => i A stable key per index, so measurements follow an item when the list reorders.
endThreshold number 0 How many items before the end onEndReached fires.
scrolling boolean (two-way) false True while the list is scrolling. Written by the list.
busy boolean false With role feed, marks the feed aria-busy. Set it while the next page loads (not while scrolling).
role "list" | "feed" | "grid" | "none" "list" Role of the track; items take the matching role (listitem, article, row). With grid, cells are gridcells and rows and cells carry aria-rowindex and aria-colindex.
label string "" Accessible name of the list.
dir "" | "ltr" | "rtl" "" Direction. A horizontal list scrolls from the right under rtl.
class string "" Extra classes on the scroll container; set its height here.
attrs Record<string, string | number | boolean | undefined> {} Other attributes for the scroll container: id, data-*, aria-*.
onRangeChange (range: number[]) => void () => {} Called when the rendered indexes change.
onScrollingChange (scrolling: boolean) => void () => {} Called when scrolling starts and stops.
onEndReached () => void () => {} Called once per count when the last item renders: load the next page here.
onVirtualizer (v: Virtualizer) => void () => {} Hands over the virtualizer: scrollToIndex, scrollToOffset, scrollBy, scrollToEnd, measure, getTotalSize.
columns number 0 Column count of a grid. Above 0, a second virtualizer on the same scroller windows the columns, and each row loops over columnRange with <VirtualCell column>.
columnRange number[] (two-way) [] The column indexes to render, written by the list as it scrolls sideways.
estimateColumnSize number | ((index: number) => number) 120 Width of each column in px. Rows of a grid are estimateSize tall.
columnOverscan number 2 Extra columns rendered on each side of the viewport.
onColumnRangeChange (range: number[]) => void () => {} Called when the rendered columns change.
onColumnVirtualizer (v: Virtualizer) => void () => {} Hands over the column virtualizer: scrollToIndex on a column.
virtualizerOptions Record<string, unknown> {} Any other virtualizer option, passed through: enabled, isScrollingResetDelay, useScrollendEvent, scrollEndThreshold, laneAssignmentMode, initialMeasurementsCache, useCachedMeasurements, initialRect, smoothScrollDuration.

VirtualCell

Attribute Type Default Description
column number (required) The cell's column index. Required.
class, attrs string, Record "", {} Extra classes and attributes on the cell.

VirtualItem

Attribute Type Default Description
index number (required) The item's index in the list. Required.
setsize number -1 aria-setsize, when the list's count is not the set size (the list sets count otherwise).
class, attrs string, Record "", {} Extra classes and attributes on the item.

Slots

Slot Content
default The rendered items: <VirtualItem e:for={i of range} index={i}>.

Keyboard

Keys Action
Tab Focuses the scroll container (or the first focusable item).
ArrowDown / ArrowUp Scroll the focused list (native scrolling).
PageDown / PageUp, Home / End Scroll by a page, or to the start and end.
Arrows (role grid) A grid with columns moves focus one cell; a cell outside the window scrolls in (one Tab stop, APG grid).
Home / End, Ctrl + Home / Ctrl + End (role grid) First or last cell of the row; first cell of the grid, last cell of the last row.
PageDown / PageUp (role grid) Move focus a viewport of rows.
PageDown / PageUp (role feed) From inside an article, focus the next or previous article (APG feed).
Ctrl + End / Ctrl + Home (role feed) Focus the first focusable element after the feed, or the last one before it.

Data attributes

Attribute Values On Description
data-orientation "vertical" | "horizontal" .virtual-list
data-scroller "self" | "window" .virtual-list
data-ready present .virtual-list The virtualizer is running; items are placed by transform.
data-scrolling present .virtual-list While scrolling.
data-index the index .virtual-item
data-lane the lane number .virtual-item
data-columns the column count .virtual-list A grid.
data-column the column index .virtual-cell
data-sticky present .virtual-item The pinned header item.

Accessibility

  • The track has role list (or feed, grid) and an aria-label from label; items take the matching item role.
  • A grid track has aria-rowcount and aria-colcount; rows have aria-rowindex and cells are gridcells with aria-colindex, so the position in the whole grid is announced.
  • There is no listbox role: a list of options needs selection and an active descendant, which Combobox, Command and Select provide.
  • Each item has aria-posinset (index + 1) and aria-setsize (count), so a screen reader announces the position in the whole list, not the rendered window.
  • The scroll container is in the Tab order so keyboard users can scroll it.
  • A feed's articles are each focusable, and the feed is aria-busy only while busy is set (a page loading).
  • A grid with columns is one Tab stop, the focused cell, and arrows move between cells, so every gridcell announced can be reached.

See also

  • DataTable (opens in a new tab): A data grid: sort, search, filter, resize, reorder, pin, group, select, edit, page or virtualize, save views and export.