npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dysonic/virtual-list

v1.1.0

Published

A lightweight React virtual list component powered by `IntersectionObserver`.

Readme

@dysonic/virtual-list

A lightweight React virtual list component powered by IntersectionObserver.

Unlike index-calculation-based virtualization libraries, this component is designed to work well with dynamic item heights. It renders visible items, keeps placeholders for offscreen items, and uses measured heights to keep the list stable while scrolling.

Features

  • Lightweight implementation based on IntersectionObserver
  • Works with dynamic-height content
  • Supports prerender buffers for smoother scrolling
  • Supports rowKey as either a field name or a function
  • React 16+ compatible

Why not react-window?

react-window is a great choice when item heights are fixed or can be calculated ahead of time. This library takes a different approach:

  • react-window mainly relies on index-based range calculation, while @dysonic/virtual-list relies on IntersectionObserver
  • react-window is best suited to fixed-size or carefully managed variable-size lists, while this component is friendlier to naturally dynamic content
  • this component measures rendered items and reuses their heights for placeholders, so mixed-height content can scroll more smoothly with less manual bookkeeping
  • the API stays small and focuses on a single list component rather than multiple layout primitives

If your list items have stable, known sizes, react-window may still be a better fit. If your list contains content with changing or hard-to-predict heights, this library is the more convenient option.

Installation

pnpm add @dysonic/virtual-list

You also need compatible peer dependencies:

pnpm add react react-dom

Development

All commands are intended to run from the repository root:

pnpm install
pnpm build
pnpm test
pnpm docs:dev

Release (Maintainers)

Set the version:

pnpm exec dy-cli version --set 1.2.3

Publish:

pnpm exec dy-cli publish

Demo

This package now includes a docs site built with dumi, following the package-local docs organization used by Ant Design:

  • 200 fixed-height rows
  • 200 dynamic-height rows with naturally changing content height

Run it locally:

pnpm docs:dev

Build the docs site:

pnpm docs:build

The docs source lives in docs.

Quick Start

import React from 'react';
import { VirtualList } from '@dysonic/virtual-list';

type User = {
  id: string;
  name: string;
  bio: string;
};

const users: User[] = [
  { id: '1', name: 'Ada', bio: 'Short bio' },
  { id: '2', name: 'Grace', bio: 'A much longer bio that makes this row taller than the others.' },
];

export function Demo() {
  return (
    <VirtualList<User>
      data={users}
      rowKey="id"
      prerender
      style={{ height: 480, overflowY: 'auto' }}
      render={(user) => (
        <article style={{ padding: 12, borderBottom: '1px solid #eee' }}>
          <h4>{user.name}</h4>
          <p>{user.bio}</p>
        </article>
      )}
    />
  );
}

Docs Examples

Demo Example: Fixed Height (200 Rows)

import React from 'react';
import { VirtualList } from '@dysonic/virtual-list';

const orders = Array.from({ length: 200 }, (_, index) => ({
  id: `fixed-${index + 1}`,
  title: `Order #${1000 + index + 1}`,
  subtitle: `Fixed-height row ${index + 1}`,
}));

export function FixedHeightDemo() {
  return (
    <VirtualList
      data={orders}
      rowKey="id"
      prerender
      minHeightOnHolderItem={72}
      style={{ height: 420, overflowY: 'auto' }}
      render={(item) => (
        <article style={{ display: 'flex', justifyContent: 'space-between', padding: 16 }}>
          <div>
            <strong>{item.title}</strong>
            <p>{item.subtitle}</p>
          </div>
        </article>
      )}
    />
  );
}

Demo Example: Dynamic Height (200 Rows)

import React from 'react';
import { VirtualList } from '@dysonic/virtual-list';

const messages = Array.from({ length: 200 }, (_, index) => ({
  id: `dynamic-${index + 1}`,
  title: `Conversation ${index + 1}`,
  paragraphs: [
    'Short content.',
    'A second paragraph makes this row taller.',
    'A third paragraph is useful when you want more obvious height differences.',
    'A fourth paragraph exaggerates the dynamic-height effect for the demo.',
  ].slice(0, (index % 4) + 1),
}));

export function DynamicHeightDemo() {
  return (
    <VirtualList
      data={messages}
      rowKey="id"
      prerender={4}
      minHeightOnHolderItem={96}
      style={{ height: 420, overflowY: 'auto' }}
      render={(item) => (
        <article style={{ padding: 16, borderBottom: '1px solid #eee' }}>
          <strong>{item.title}</strong>
          {item.paragraphs.map((paragraph) => (
            <p key={paragraph}>{paragraph}</p>
          ))}
        </article>
      )}
    />
  );
}

How it Works

The component renders real items for entries that are currently intersecting the viewport. Items outside the viewport are replaced with placeholder elements. Once an item has been rendered, its measured height is cached and reused by the placeholder, which helps maintain scroll continuity even when item heights are different.

When prerender is enabled, the component also keeps a small buffer of nearby items mounted before and after the visible range to reduce flicker during fast scrolling.

API

VirtualList<T>

| Prop | Type | Default | Description | | --- | --- | --- | --- | | data | T[] | - | The full list of items to render | | rowKey | keyof T \| (item: T) => string | - | Unique key for each item | | render | (item: T, index: number) => React.ReactNode | - | Render function for each list item | | prerender | boolean \| number | false | Enables prerendering. When a number is provided, it is used as the buffer size | | prerenderBufferSize | number | 2 | Buffer size used when prerender={true} | | maxHolderItems | number | 50 | Maximum number of placeholder items to keep in the DOM when prerendering | | minHeightOnHolderItem | number | 150 | Fallback placeholder height before an item has been measured | | className | string | - | Class name applied to the outer wrapper | | holderItemClassName | string | - | Class name applied to placeholder items | | style | React.CSSProperties | - | Inline styles applied to the outer wrapper |

Recommended Usage

  • Wrap the list in a scrollable container with an explicit height
  • Use a stable rowKey
  • Enable prerender when users may scroll quickly
  • Tune minHeightOnHolderItem when your average item height is known
  • Increase maxHolderItems if you notice visible height jumps during fast scrolling

Notes

  • The component depends on IntersectionObserver. For older environments, include a polyfill if needed
  • Dynamic-height support works best when each item eventually renders to a stable height
  • Very inaccurate minHeightOnHolderItem values may cause temporary scroll jumps before enough items have been measured

License

MIT