Virtual List
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.
tsx
import { VirtualList } from '@nofinite/nui';Interactive Preview
Loading preview...
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
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> | — | — |