<arc-virtual-list> Overview
Guidelines
When to use
- Use for lists with 100+ items where full DOM rendering would cause jank
- Set `item-height` to match the actual rendered height of each item
- Use overscan of 3-10 items — higher values reduce flicker but increase DOM nodes
- Combine with arc-list-item for consistent styling within the virtual container
When not to use
- Use for short lists under 50 items — the overhead is not worth it
- Mix different item heights — virtual-list requires fixed row height
- Nest scrollable containers inside virtual-list items
- Forget to set a fixed height on the virtual-list host element
Features
- Windowed rendering — only visible items are in the DOM
- Handles tens of thousands of items with constant DOM node count
- rAF-throttled scroll handler for smooth 60fps performance
- Configurable overscan buffer to prevent flicker during fast scrolling
- Fixed item height for predictable layout calculations
- Named slot pattern for flexible item templates
- `visibleRange` getter for external rendering integration
- Exposed CSS parts: spacer, item
Preview
Usage
This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.
<arc-virtual-list
id="my-list"
item-height="48"
overscan="5"
style="height: 400px;"
></arc-virtual-list>
<script type="module">
const vl = document.getElementById('my-list');
const data = Array.from({ length: 10000 }, (_, i) => `Row ${i + 1}`);
vl.items = data;
// Sync slotted DOM elements to the visible window
function syncSlots() {
const { start, end } = vl.visibleRange;
// Remove items that scrolled out of view
for (const child of [...vl.children]) {
const idx = parseInt(child.slot.replace('item-', ''), 10);
if (idx < start || idx >= end) child.remove();
}
// Add items that scrolled into view
for (let i = start; i < end; i++) {
if (!vl.querySelector(`[slot="item-${i}"]`)) {
const div = document.createElement('div');
div.slot = `item-${i}`;
div.textContent = data[i];
vl.appendChild(div);
}
}
}
vl.addEventListener('scroll', () => requestAnimationFrame(syncSlots));
vl.updateComplete.then(syncSlots);
</script> import { useRef, useEffect, useCallback } from 'react';
import { VirtualList } from '@arclux/arc-ui-react';
const data = Array.from({ length: 10000 }, (_, i) => `Row ${i + 1}`);
function MyVirtualList() {
const ref = useRef<any>(null);
const [range, setRange] = useState({ start: 0, end: 20 });
const onScroll = useCallback(() => {
const vl = ref.current;
if (vl) setRange({ ...vl.visibleRange });
}, []);
return (
<VirtualList ref={ref} items={data} item-height={48} style={{ height: '400px' }}
onScroll={onScroll}>
{data.slice(range.start, range.end).map((label, i) => (
<div key={range.start + i} slot={`item-${range.start + i}`}>{label}</div>
))}
</VirtualList>
);
} <script setup>
import { VirtualList } from '@arclux/arc-ui-vue';
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Row ${i + 1}`,
}));
</script>
<template>
<VirtualList :items="items" item-height="48" :overscan="5" style="height: 400px" />
</template> <script>
import { VirtualList } from '@arclux/arc-ui-svelte';
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Row ${i + 1}`,
}));
</script>
<VirtualList {items} item-height={48} overscan={5} style="height: 400px" /> import { Component } from '@angular/core';
import { VirtualList } from '@arclux/arc-ui-angular';
@Component({
imports: [VirtualList],
template: `
<VirtualList [items]="items" item-height="48" [overscan]="5" style="height: 400px" />
`,
})
export class LargeListComponent {
items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Row ${i + 1}`,
}));
} import { VirtualList } from '@arclux/arc-ui-solid';
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Row ${i + 1}`,
}));
<VirtualList items={items} item-height={48} overscan={5} style={{ height: '400px' }} /> import { VirtualList } from '@arclux/arc-ui-preact';
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Row ${i + 1}`,
}));
<VirtualList items={items} item-height={48} overscan={5} style={{ height: '400px' }} /> API
| Prop | Type | Default | Description |
|---|---|---|---|
items | Array | [] | The full data array. Only the visible slice is rendered at any given time. |
item-height | number | 40 | Height in pixels of each item row. Must match the actual rendered height. |
overscan | number | 5 | Number of extra items to render above and below the visible window to reduce flicker. |
See Also
- List Structured list container with optional selection, keyboard navigation, and multiple visual variants. Pairs with arc-list-item for rich content rows.
- Data Table A data-driven table component that renders rows from a JavaScript array. Declarative column definitions via `arc-column` children control which fields appear, their headers, widths, and sort behavior. Built-in support for column sorting, row selection with checkboxes, and an empty-state fallback.
- Infinite Scroll Intersection Observer-powered container that fires a load event when the user scrolls near the bottom, with built-in loading spinner and end-of-list state.