bigbullui logobigbullui
Componentdata

VirtualList

Windowed list for fixed-height rows: scrollTop math, visible slice renders, spacer divs; smooth scroll.

Preview

Live Preview
Item 0
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Item 7
Item 8
Item 9
Item 10
 

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add virtual-list

Or install the full npm package:

npm install bigbullui

Usage

import { VirtualList } from "@/components/ui/virtual-list"

<VirtualList
  items={Array.from({ length: 100 }, (_, i) => i)}
  itemHeight={40}
  height={400}
  render={(item) => <div key={item} className="p-2 bg-muted text-sm">Item {item}</div>} />

Source code

virtual-list.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export type VirtualListProps<T> = {
  items: T[];
  itemHeight: number;
  height: number;
  render: (item: T) => React.ReactNode;
};

export function VirtualList<T>({ items, itemHeight, height, render }: VirtualListProps<T>) {
  const [scrollTop, setScrollTop] = React.useState(0);
  const containerRef = React.useRef<HTMLDivElement>(null);

  const visibleStart = Math.max(0, Math.floor(scrollTop / itemHeight) - 1);
  const visibleEnd = Math.min(
    items.length,
    Math.ceil((scrollTop + height) / itemHeight) + 1,
  );

  const style = {
    transform: `translateY(${scrollTop}px)`,
  };

  const spacerHeight = scrollTop + height + itemHeight;

  return (
    <div
      ref={containerRef}
      className={cn("relative overflow-auto", "motion-reduce:animate-none")}
      onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
      style={{ height }}
    >
      <div
        className={cn(
          "relative",
          "motion-reduce:animate-none",
          "motion-reduce:transition-none",
        )}
        style={{
          height,
          transform: `translateY(${-scrollTop}px)`,
        }}
      >
        {items.slice(visibleStart, visibleEnd).map((item, index) => (
          <div
            key={index}
            className={cn("p-3 bg-card", "motion-reduce:animate-none")}
            style={{ height: itemHeight }}
          >
            {render(item)}
          </div>
        ))}

        <div
          className={cn("p-3 bg-muted", "motion-reduce:animate-none")}
          style={{ height: spacerHeight }}
        >
          &nbsp;
        </div>
      </div>
    </div>
  );
}

Props

PropTypeDescription
itemsT[]List of items to render.
itemHeightnumberHeight of each item in pixels.
heightnumberTotal visible height of the list container in pixels.
render(item: T) => React.ReactNodeRenderer function for each item.