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

@ceriousdevtech/react-cerious-scroll

v1.0.6

Published

React bindings for CeriousScroll — high-performance virtual scrolling with O(1) memory and no height estimation

Readme

@ceriousdevtech/react-cerious-scroll

License Live Demo

React bindings for Cerious Scroll™ — high-performance virtual scrolling with O(1) memory, consistent 60 FPS+, and native variable-height support with no height estimation.

Rows are rendered into the engine's own measured containers via React portals and committed synchronously, so every row's real height is measured (never estimated) — exactly the guarantee that makes CeriousScroll precise. Because rows stay in your React tree, Context / providers work normally.


Installation

npm install @ceriousdevtech/react-cerious-scroll @ceriousdevtech/cerious-scroll

react and react-dom (>= 18) are peer dependencies.


Demo

Live demo → — 100,000 rows, fixed/variable-height toggle, imperative jump-to-row, and live viewport stats.

To run locally:

npm install
npm run demo        # dev server with HMR
npm run demo:build  # production build to demo/dist

The demo imports the wrapper by its package name, aliased to the library source, so edits to src/ are reflected live.


Quick start (component)

Give the container a height; provide items and a renderItem render prop.

import { CeriousScroll } from '@ceriousdevtech/react-cerious-scroll';

const items = Array.from({ length: 1_000_000 }, (_, i) => ({ id: i, name: `Item ${i}` }));

export function List() {
  return (
    <CeriousScroll
      items={items}
      renderItem={(item, index) => (
        <div className="row">
          {index} — {item.name}
        </div>
      )}
      style={{ height: 480 }}
    />
  );
}

Variable heights need no configuration — just render rows of whatever height; the engine measures each one.

Without a full array (huge / sparse data)

<CeriousScroll
  totalElements={100_000_000}
  getItem={(index) => loadRow(index)}
  renderItem={(row, index) => <Row data={row} index={index} />}
  style={{ height: 600 }}
/>

Hook

useCeriousScroll gives you full control. Attach containerRef to your scroll element and render portals somewhere in your tree (they attach to their own DOM targets, so placement only affects which React Context they inherit).

import { useCeriousScroll } from '@ceriousdevtech/react-cerious-scroll';

function List() {
  const { containerRef, portals } = useCeriousScroll({
    items,
    renderItem: (item, index) => <Row item={item} index={index} />,
  });

  return (
    <div ref={containerRef} style={{ height: 480, position: 'relative', overflow: 'hidden' }}>
      {portals}
    </div>
  );
}

Component props

| Prop | Type | Description | | --- | --- | --- | | renderItem | (item, index) => ReactNode | Required. Renders one row. item is undefined if no data source is given. | | items | readonly TItem[] | Optional data array. totalElements defaults to items.length. | | totalElements | number | Total item count. Required if items is omitted. | | getItem | (index) => TItem | Lazy item getter for large/sparse datasets. | | tableHeader | ReactNode | Table mode only. A <tr> of <th>s rendered into the engine's <thead> (see Table layout). | | options | CeriousScrollOptions | Engine options (keyboard/touch/wheel/scrollbar/layout/etc.). Read once at creation. | | autoRender | boolean | Re-render on scroll/resize/data changes. Default true. | | onViewportChange | (detail) => void | Normalized viewport-change callback. | | onMeasuredViewport | (range) => void | Measured range after each render pass. | | onReady | (scroller) => void | The underlying engine instance, once ready. | | className / style | — | Applied to the scroll container (set a height!). |

Imperative handle (via ref)

const ref = useRef<CeriousScrollHandle>(null);
// ref.current?.jumpToElement(500);
// ref.current?.scrollToPercentage(50);
// ref.current?.reset();
// ref.current?.render();
// ref.current?.recalculate(); // drop cached heights + re-measure (see Notes)
// ref.current?.scroller;      // the raw engine

Table layout

Pass options={{ layout: 'table' }} to render real <table> / <tr> / <td> rows with a frozen header and native column alignment. Your renderItem returns the row's <td> cells, and tableHeader provides the (declarative, reactive) <thead> row:

import { TABLE_COLUMNS } from './data';

<CeriousScroll
  className="my-scroll"            // give it a height
  totalElements={100_000}
  getItem={(i) => i}
  options={{ layout: 'table', table: { tableClassName: 'my-table', autoSizeColumns: true } }}
  tableHeader={
    <tr>{TABLE_COLUMNS.map((c) => <th key={c.key}>{c.label}</th>)}</tr>
  }
  renderItem={(index) => {
    const row = makeRow(index);
    return (
      <>
        <td>{row.id}</td>
        <td>{row.name}</td>
        <td>{row.email}</td>
      </>
    );
  }}
/>
  • tableHeader is portaled into the engine's <thead> — the same <table> as the rows, so columns align natively and the header stays frozen.
  • renderItem must return <td>s (a fragment of cells). They're rendered into the row's <tr> via a display: contents wrapper, so React fully owns them and the engine's row recycling can't tear them out.
  • table.autoSizeColumns measures column widths once and pins them — auto-sized but stable (no jitter, no manual widths). Or use table.columnWidths. Variable row heights work as usual.
  • CSS: border-collapse: separate and an opaque <thead> background (see the core README's Table Layout notes).

Notes

  • No height estimation. Rows are committed with flushSync so the engine measures real offsetHeight. Later size changes are picked up by the engine's built-in ResizeObserver.
  • options are read at creation. Changing options after mount has no effect; remount (e.g. with a key) to apply new engine options.
  • Changing the item count recreates the engine internally (scroll position is preserved). Mutating items without changing the count just re-renders the content (cheap, and your row state is preserved) — it does not discard cached heights, so editable grids that produce a new items array on every edit don't trigger a full viewport re-measure.
  • If every rendered row's height changes at once (e.g. a density/layout switch) the cached heights become stale and rows can misalign until the next scroll. Call recalculate() (on the ref, or from the hook result) right after the change to drop the height cache and re-measure. Don't call it on routine edits — a single cell edit keeps its row's size, and the engine's built-in ResizeObserver picks up any incidental resize on its own.

License

Licensed by Cerious DevTech LLC under the MIT License (see LICENSE-MIT).

📧 [email protected]