VirtualList
Efficiently render large lists by only rendering visible items. Powered by @tanstack/react-virtual.
Installation
Section titled “Installation”This component requires @tanstack/react-virtual as a peer dependency:
npm install @tanstack/react-virtualImport
Section titled “Import”import { VirtualList } from 'asterui/virtuallist'Examples
Section titled “Examples”Basic Usage
Efficiently render a list of 10,000 items.
import { VirtualList } from 'asterui/virtuallist'
function App() {
const basicItems = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i + 1}`
}))
return (
<VirtualList
items={basicItems}
height={300}
itemHeight={40}
className="border border-base-300 rounded-lg"
renderItem={(item) => (
<div className="p-2 border-b border-base-300 flex items-center h-full">
{item.name}
</div>
)}
/>
)
}
export default App Styled List
With card styling and hover effects.
import { VirtualList } from 'asterui/virtuallist'
function App() {
const users = Array.from({ length: 5000 }, (_, i) => ({
id: i,
name: `User ${i + 1}`,
email: `user${i + 1}@example.com`
}))
return (
<VirtualList
items={users}
height={300}
itemHeight={60}
className="border border-base-300 rounded-lg"
renderItem={(user) => (
<div className="p-3 hover:bg-base-200 transition-colors border-b border-base-300 h-full flex flex-col justify-center">
<div className="font-medium">{user.name}</div>
<div className="text-sm text-base-content/60">{user.email}</div>
</div>
)}
/>
)
}
export default App With Gap
Spaced items with gap between them.
import { VirtualList } from 'asterui/virtuallist'
function App() {
const cardItems = Array.from({ length: 1000 }, (_, i) => ({
id: i,
title: `Card ${i + 1}`,
description: 'A brief description'
}))
return (
<VirtualList
items={cardItems}
height={300}
itemHeight={72}
gap={8}
className="p-2 border border-base-300 rounded-lg"
renderItem={(item) => (
<div className="card bg-base-200 p-3 h-full flex flex-col justify-center">
<h3 className="font-bold">{item.title}</h3>
<p className="text-sm text-base-content/70">{item.description}</p>
</div>
)}
/>
)
}
export default App Scroll Callback
Track scroll position for infinite loading or analytics.
Scroll position: 0px
import { VirtualList } from 'asterui/virtuallist'
import { useState } from 'react'
function App() {
const scrollItems = Array.from({ length: 10000 }, (_, i) => ({ id: i }))
const [scrollTop, setScrollTop] = useState(0)
return (
<div>
<div className="mb-2 text-sm text-base-content/70">Scroll position: {Math.round(scrollTop)}px</div>
<VirtualList
items={scrollItems}
height={250}
itemHeight={40}
onScroll={setScrollTop}
className="border border-base-300 rounded-lg"
renderItem={(_, index) => (
<div className="p-2 border-b border-base-300 flex items-center h-full">
Row {index + 1}
</div>
)}
/>
</div>
)
}
export default App Variable Height (Chat)
Chat messages with different lengths using the Chat component.
import { Chat } from 'asterui'
import { VirtualList } from 'asterui/virtuallist'
function App() {
const chatMessages = [
{ id: 1, user: 'Alex', text: 'Hey!' },
{ id: 2, user: 'You', text: 'Hi Alex, what\'s up?' },
{ id: 3, user: 'Alex', text: 'Not much, just wanted to check if you\'re coming to the party on Saturday' },
{ id: 4, user: 'You', text: 'Oh yeah, I\'ll be there! What time does it start?' },
{ id: 5, user: 'Alex', text: '8pm' },
{ id: 6, user: 'You', text: 'Cool' },
{ id: 7, user: 'Alex', text: 'Can you bring some snacks? We\'re running low on chips and stuff. Maybe grab a couple bags of tortilla chips and some salsa if you can find good ones' },
{ id: 8, user: 'You', text: 'Sure thing, I\'ll stop by the store on the way' },
{ id: 9, user: 'Alex', text: 'Thanks!' },
{ id: 10, user: 'You', text: 'No problem. Who else is coming?' },
{ id: 11, user: 'Alex', text: 'Sarah, Mike, probably Jordan. Maybe a few others' },
{ id: 12, user: 'You', text: 'Nice' },
{ id: 13, user: 'Alex', text: 'Yeah it should be fun. We\'re gonna set up the projector in the backyard for a movie later if the weather holds up' },
{ id: 14, user: 'You', text: 'What movie?' },
{ id: 15, user: 'Alex', text: 'Haven\'t decided yet. Any suggestions?' },
{ id: 16, user: 'You', text: 'How about something funny? We could do a comedy' },
{ id: 17, user: 'Alex', text: 'Good idea' },
{ id: 18, user: 'You', text: 'Alright, see you Saturday then!' },
{ id: 19, user: 'Alex', text: 'See ya!' },
{ id: 20, user: 'You', text: '👋' },
]
return (
<VirtualList
items={chatMessages}
height={300}
itemHeight={(msg) => 70 + Math.floor(msg.text.length / 40) * 24}
className="bg-base-100 border border-base-300 rounded-lg p-2"
renderItem={(msg) => (
<Chat
position={msg.user === 'You' ? 'end' : 'start'}
header={msg.user}
message={msg.text}
color={msg.user === 'You' ? 'primary' : undefined}
/>
)}
/>
)
}
export default App VirtualList
Section titled “VirtualList”| Property | Description | Type | Default |
|---|---|---|---|
items | Array of items to render | T[] | - |
height | Height of the scrollable container | number | string | - |
itemHeight | Fixed height, or function returning estimated height per item for variable heights | number | ((item: T, index: number) => number) | - |
renderItem | Render function for each item | (item: T, index: number) => ReactNode | - |
overscan | Number of items to render outside visible area | number | 5 |
gap | Gap between items in pixels | number | 0 |
width | Width of the container | number | string | - |
className | Additional class for the scroll container | string | - |
innerClassName | Additional class for the inner container | string | - |
itemClassName | Additional class for each item wrapper | string | - |
onScroll | Callback when scroll position changes | (scrollTop: number) => void | - |
data-testid | Test ID for testing | string | - |
Performance Tips
Section titled “Performance Tips”- Use a fixed
itemHeightwhen possible for best performance - Memoize your
renderItemfunction if it’s expensive - Increase
overscanif you see flickering during fast scrolling - Keep item components simple - avoid heavy computations in render