Skip to content
ARC UI ARC Reactive Components
Docs Components Tokens Synthesizer
v2.1.0
Getting Started Frameworks Design Tokens Theming Theme Synthesizer Accessibility Browser Support Changelog Contributing All Components App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageInfinite ScrollMarqueeScroll AreaSeparatorSkeletonSpinnerStackVirtual List Animated NumberBadgeComparisonCountdown TimerData TableDescription ListDiffKey ValueListMeterSparklineStatStepperTableTagTimelineValue Card BlockquoteCode BlockGradient TextHighlightKbdMarkdownNumber FormatProseTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerFieldsetFile UploadFormHotkeyIcon ButtonInputInput GroupLabelMulti SelectNumber InputOTP InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSliderSortable ListSwitch GroupTextareaTheme ToggleTime PickerToggle AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip
Components Virtual List
content interactive
<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