Virtual List
DnD & InteractionVirtualized list for rendering large datasets.
Preview
Row 1 — Virtualised for performance
Row 2 — Virtualised for performance
Row 3 — Virtualised for performance
Row 4 — Virtualised for performance
Row 5 — Virtualised for performance
Row 6 — Virtualised for performance
Row 7 — Virtualised for performance
Row 8 — Virtualised for performance
Row 9 — Virtualised for performance
Row 10 — Virtualised for performance
Row 11 — Virtualised for performance
Row 12 — Virtualised for performance
Row 13 — Virtualised for performance
Row 14 — Virtualised for performance
Row 15 — Virtualised for performance
Row 16 — Virtualised for performance
Usage
example.jsx
import { VirtualList } from "@/components/ui/virtual-list";
export default function Example() {
return <VirtualList />;
}Source Code
Copy this file into components/ui/virtual-list.jsx in your project.
virtual-list.jsx
"use client";
import { forwardRef, useState, useRef, useCallback, useEffect } from "react";
import { cn } from "@/lib/utils";
const VirtualList = forwardRef(({ className, items = [], itemHeight = 40, overscan = 5, renderItem, height = 400, ...props }, ref) => {
const containerRef = useRef(null);
const [scrollTop, setScrollTop] = useState(0);
const totalHeight = items.length * itemHeight;
const visibleCount = Math.ceil(height / itemHeight);
const startIdx = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
const endIdx = Math.min(items.length, startIdx + visibleCount + overscan * 2);
const visibleItems = items.slice(startIdx, endIdx);
const onScroll = useCallback((e) => setScrollTop(e.currentTarget.scrollTop), []);
return (
<div ref={(el) => { containerRef.current = el; if (typeof ref === "function") ref(el); else if (ref) ref.current = el; }}
className={cn("overflow-auto rounded-md border", className)}
style={{ height }}
onScroll={onScroll}
{...props}
>
<div style={{ height: totalHeight, position: "relative" }}>
{visibleItems.map((item, i) => (
<div key={startIdx + i}
style={{ position: "absolute", top: (startIdx + i) * itemHeight, height: itemHeight, left: 0, right: 0 }}
>
{renderItem ? renderItem(item, startIdx + i) : <div className="flex items-center px-3 text-sm">{String(item)}</div>}
</div>
))}
</div>
</div>
);
});
VirtualList.displayName = "VirtualList";
export { VirtualList };
Quick Install
Make sure you have the cn() utility set up. It requires clsx and tailwind-merge.
npm install clsx tailwind-merge