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

another-react-responsive-masonry

v0.1.0-alpha.3

Published

A React virtualized responsive masonry library built on top of @tanstack/virtual

Downloads

7

Readme

another-react-responsive-masonry

A React virtualized responsive masonry library built on top of @tanstack/virtual (commit: 94761ff)

Demo video:

https://github.com/user-attachments/assets/39194a80-004f-4877-9e31-dad65f751ba9

Why Another Masonry Library?

A truly effective masonry layout needs three critical features:

  1. 📱 Responsive - Adapts to different screen sizes with varying column counts
  2. Virtualized - Only renders visible items for optimal performance with large datasets
  3. ⚖️ Height-balanced - Distributes items intelligently across columns to maintain visual balance

Most existing React masonry libraries fall short in one or more of these areas. The majority use simple CSS Grid or Flexbox approaches, which merely assign items to columns in sequence without considering height distribution. Some offer virtualization, but very few actually measure and calculate item heights to balance layouts effectively.

This library builds upon @tanstack/virtual, which already handles virtualization and height balancing (the most technically challenging features). However, @tanstack/virtual lacks proper responsive support - changing the number of lanes on window resize causes issues. By extending it with custom modifications, this library has tried its best to solve all three requirements.

Design decision: Instead of providing a high-level, opinionated component, this library exports separate low-level hooks to give developers maximum flexibility and control. They're the same as original library's hooks with minimal adjustments to retain the powerful customization capabilities.

Key Modules

useBreakpoint

Located in packages/lib/src/hooks/useBreakpoint.tsx, this hook manages responsive breakpoints by:

  • Accepting an array of breakpoint configurations with minWidth and nCol (number of columns)
  • Using window.matchMedia to detect the current breakpoint
  • Providing debounced updates to prevent excessive re-renders during window resizing
  • Exporting a context provider for sharing breakpoint state across components

Modified @tanstack/virtual

The packages/lib/src/tanstack/ directory contains the modified source code from @tanstack/virtual. The patch file shows the exact changes made:

Major modifications:

  1. Per-layout measurement caching - The biggest architectural change is how item measurements are stored. Each unique number of lanes (columns) is treated as a separate layout, with its own independent measurement cache (lanesCache array). This ensures that when the window is resized but stays within the same breakpoint (same number of lanes), items remain in their assigned lanes without recalculation.

  2. Stable lane assignment - Within the same layout, each item's lane assignment remains constant across resizes, preventing jarring layout shifts.

  3. Updated getVirtualItems return - Now returns { virtualItems, lanes } instead of just the items array, providing access to the current number of lanes.

  4. Debounced resize handling - Added resizeDelay option that debounces re-renders during window resize, improving performance and reducing visual flickering.

  5. Enhanced estimateSize function - The function signature now includes the number of lanes as a parameter: estimateSize(index, lanes). This allows more accurate initial size estimates based on the current column configuration. Providing a good estimate function is crucial for optimal performance.

Usage

npm install another-react-responsive-masonry

See the example source code in packages/example/src for a better understanding of how to use the library.

Usage is nearly identical to @tanstack/virtual. In most cases you can easily import it as a drop-in replacement and keep most of your existing code while gaining good responsive masonry behavior.

Quick Start:

import { useBreakpoint, useWindowVirtualizer, VirtualItem } from 'another-react-responsive-masonry';

const breakpointColumns = [
  { name: 'mobile', minWidth: 0, nCol: 1 },
  { name: 'small', minWidth: 480, nCol: 2 },
  { name: 'medium', minWidth: 768, nCol: 3 },
  { name: 'large', minWidth: 1024, nCol: 4 },
  { name: 'xlarge', minWidth: 1280, nCol: 5 },
];

function useMasonry(items: CardItem[], rowGap: number) {
  const { currentBreakpoint } = useBreakpoint(breakpointColumns);
  const columnRef = useRef<HTMLDivElement>(null);

  const [enabled, setEnabled] = useState(false);
  useEffect(() => {
    // Only enable after elements are mounted
    setEnabled(true);
  }, []);

  const rowVirtualizer = useWindowVirtualizer({
    enabled,
    count: items.length,
    overscan: 10,
    scrollMargin: columnRef.current?.offsetTop,
    lanes: currentBreakpoint.nCol,
    gap: rowGap,
    useAnimationFrameWithResizeObserver: true,
    resizeDelay: 50,
    estimateSize: (i) => {
      return items[i].estimateHeight(columnRef.current?.clientWidth || 300);
    },
  });

  const { virtualItems, lanes } = rowVirtualizer.getVirtualItems();

  const columns = useMemo(() => {
    const arr = Array.from({ length: lanes }, () => [] as VirtualItem[]);
    for (const item of virtualItems) {
      arr[item.lane].push(item);
    }
    return arr;
  }, [virtualItems, lanes]);

  return { columnRef, columns, rowVirtualizer };
}

function MasonryContainer() {
  const [items, setItems] = useState<CardItem[]>(() => generateSampleItems(50));

  const rowGap = 10;
  const columnGap = 20;
  const { columnRef, columns, rowVirtualizer } = useMasonry(items, rowGap);

  return (
    <>
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: `repeat(${columns.length}, 1fr)`,
          gap: columnGap,
          width: '100%',
          height: rowVirtualizer.getTotalSize(),
        }}
      >
        {columns.map((column, index) => (
          <div
            ref={index === 0 ? columnRef : undefined}
            key={index}
            style={{
              display: 'flex',
              flexDirection: 'column',
              position: 'relative',
            }}
          >
            {column.map((virtualItem) => {
              const { estimateHeight: _, ...props } = items[virtualItem.index];
              return (
                <Card
                  key={props.id}
                  measureElement={rowVirtualizer.measureElement}
                  dataIndex={virtualItem.index}
                  offsetY={virtualItem.start - rowVirtualizer.options.scrollMargin}
                  {...props}
                />
              );
            })}
          </div>
        ))}
      </div>
    </>
  );
}

License

MIT