Virtual List
A composable, accessible VirtualList component for modern React applications.
tsx
import { VirtualList } from '@nofinite/nui';Interactive Preview
VirtualList (High performance rendering)
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
tsx
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
tsx
<VirtualList
items={items}
height={400}
itemHeight={60}
renderItem={(item) => <div>{item}</div>}
/>With overscan for smoother scrolling
tsx
<VirtualList
items={items}
height={400}
overscan={5}
renderItem={(item) => <div>{item}</div>}
/>Using key extractor
tsx
<VirtualList
items={items}
height={400}
keyExtractor={(item, index) => `key-${index}`}
renderItem={(item) => <div>{item}</div>}
/>Variants
Large datasets
tsx
const items = Array.from({ length: 10000 }, (_, i) => `Row ${i + 1}`);
<VirtualList
items={items}
height={500}
renderItem={(item) => <div>{item}</div>}
/>;Custom content per row
tsx
<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
keyExtractorwhen using dynamic items - Keep
itemHeightconsistent 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 containerrole="listitem"on each row- Fully scrollable via keyboard or pointer
- Supports focusable interactive content inside rows
- Uses semantic roles for screen readers
ze | | --fg-default | Row text color | | --bg-subtle | Row hover background | | --space-4 | Row 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
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
items* | T[] | — | Array of data items to render |
height | number | — | Height of the scrollable viewport in pixels. If omitted, it will automatically size to its container. |
itemHeight | number | — | Fixed height for each row in pixels. Defaults to 40. |
overscan | number | — | Extra rows to render above and below the viewport to prevent white flashes during fast scrolling |
keyExtractor | ((item: T, index: number) => string | number) | — | Extracts a unique React key for each item. Crucial for stable rendering during sorts/filters! |
renderItem* | (item: T, index: number) => ReactNode | — | The render function for the row |
ref | ForwardedRef<VirtualListHandle> | — | — |