masonix
Components

MasonryVirtual

Virtualized masonry for large feeds, infinite loading, and fast scrolling.

MasonryVirtual computes masonry placement while rendering only the items near the viewport. It lives in masonix/virtual so virtualization code is included only when you import it.

When to use

  • Feeds with hundreds or thousands of items.
  • App panels where scroll performance matters.
  • Layouts that need infinite loading or imperative jump-to-item controls.

Basic usage

import { useRef } from 'react';
import { MasonryVirtual } from 'masonix/virtual';

export function Feed({ items }: { items: FeedItem[] }) {
  const scrollContainerRef = useRef<HTMLDivElement>(null);

  return (
    <div
      ref={scrollContainerRef}
      className="h-96 min-w-0 overflow-x-hidden overflow-y-auto overscroll-contain rounded-lg border p-3"
    >
      <MasonryVirtual
        items={items}
        scrollContainer={scrollContainerRef}
        columns={{ 0: 1, 620: 2 }}
        gap={12}
        estimatedItemHeight={220}
        itemKey={(item) => item.id}
        render={({ data }) => <FeedCard item={data} />}
      />
    </div>
  );
}

Infinite loading

<MasonryVirtual
  items={items}
  totalItems={totalCount}
  columns={{ 0: 1, 600: 2 }}
  gap={12}
  estimatedItemHeight={220}
  endReachedThreshold={4}
  onEndReached={() => loadMore()}
  render={({ data }) => <FeedCard item={data} />}
/>

onEndReached fires when the rendered range reaches the threshold, and it does not repeatedly fire for the same loaded item count.

Imperative scrolling

import { useRef } from 'react';
import type { MasonryVirtualHandle } from 'masonix/virtual';

const scrollRef = useRef<MasonryVirtualHandle>(null);

scrollRef.current?.scrollToIndex(42, {
  align: 'center',
  smooth: true,
});

Pass the ref with scrollRef={scrollRef}.

Common mistakes

  • Do not forget estimatedItemHeight; virtual layouts need an offscreen estimate.
  • Do not use window scrolling assumptions when the feed lives in a panel; pass scrollContainer.
  • Do not render expensive cards during fast scrolling if a scroll-seek placeholder would work.

On this page