# VirtualList A long list, or a grid of rows and columns, that renders only the items near the viewport, with measured row heights. ```ehtml Example import { VirtualList, VirtualItem, VirtualCell, initialRange } from "@elements/ui/virtual-list"; people[i].id} index={i}> {people[i].name} ... r} index={r}> c} column={c}>{r}, {c} ``` ## 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 ``. 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` | `{}` | 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 ``. | | `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` | `{}` | 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: ``. | ## 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](data-table): A data grid: sort, search, filter, resize, reorder, pin, group, select, edit, page or virtualize, save views and export.