Skip to main content

VirtualList

A high-performance scrollable list component that efficiently renders only visible rows. Supports dynamic row virtualization, custom item rendering, fixed row heights, and large datasets without performance degradation.


Usage

Basic usage

import { VirtualList } from '@nofinite/nui';

const items = Array.from({ length: 1000 }, (_, i) => `Item ${i + 1}`);

<VirtualList
items={items}
height={400}
renderItem={(item) => <div>{item}</div>}
/>;

With custom row height

<VirtualList
items={items}
height={400}
itemHeight={60}
renderItem={(item) => <div>{item}</div>}
/>

With overscan for smoother scrolling

<VirtualList
items={items}
height={400}
overscan={5}
renderItem={(item) => <div>{item}</div>}
/>

Using key extractor

<VirtualList
items={items}
height={400}
keyExtractor={(item, index) => `key-${index}`}
renderItem={(item) => <div>{item}</div>}
/>

Props

PropTypeDefaultDescription
itemsT[]Array of data items to render
heightnumberHeight of the scrollable viewport in pixels
itemHeightnumber40Fixed height of each row in pixels
overscannumber3Extra rows rendered above and below viewport
keyExtractor(item: T, index: number) => string | numberReturns a unique key for each item
renderItem(item: T, index: number) => ReactNodeFunction to render each row
classNamestringAdditional styling for the container

Variants

Large datasets

const items = Array.from({ length: 10000 }, (_, i) => `Row ${i + 1}`);

<VirtualList
items={items}
height={500}
renderItem={(item) => <div>{item}</div>}
/>;

Custom content per row

<VirtualList
items={items}
height={400}
renderItem={(item) => (
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{item}</span>
<button>Edit</button>
</div>
)}
/>

Available variants

  • Fixed row height (default 40px)
  • Custom row heights (via itemHeight)
  • Overscan for smooth scrolling
  • Key extractor for dynamic items
  • Render any JSX per row

Guidelines

  • Use for large datasets where full DOM rendering is inefficient
  • Always provide keyExtractor when using dynamic items
  • Keep itemHeight consistent for best performance
  • Use overscan to prevent flickering when scrolling

States

  • Default (visible portion rendered)
  • Hover (row highlight)
  • Scroll (virtualization offsets applied)

Keyboard Interaction

  • Standard browser scrolling applies
  • Focusable elements inside rows can receive keyboard events
  • Container supports tab navigation if interactive elements exist inside rows

Accessibility

  • role="list" on container
  • role="listitem" on each row
  • Fully scrollable via keyboard or pointer
  • Supports focusable interactive content inside rows
  • Uses semantic roles for screen readers

Design Tokens

TokenUsage
--nui-bg-surfaceContainer background
--nui-border-defaultRow borders & container border
--nui-radius-mdContainer border radius
--nui-font-sansFont family
--nui-text-smRow text size
--nui-fg-defaultRow text color
--nui-bg-subtleRow hover background
--nui-space-4Row horizontal padding

Best Practices

Do

  • Use for very large lists (thousands of rows)
  • Keep row heights consistent for optimal virtualization
  • Provide keyExtractor for dynamic or filtered items
  • Combine with interactive row content safely
  • Use overscan to prevent flickering

Don’t

  • Use for very small lists where regular mapping is sufficient
  • Mix variable heights unless properly calculated
  • Overload rows with heavy components causing render jank
  • Forget to provide unique keys for dynamic items
  • Remove scrollable container or overflow properties