masonix
Components

MasonryBalanced

Measured shortest-column placement for mixed-height cards.

MasonryBalanced measures item heights and places each item into the shortest column. Use it when uneven card heights make a regular grid feel lopsided.

Performance

Designing feeds that still feel fast at 10,000 items

A practical guide to windowing, measurement, and when placeholders improve the scroll experience.

7 min read

Images

Treat image dimensions as layout data

Known width and height values let the layout settle before media finishes loading.

4 min read

Accessibility

Masonry grids can still be semantic

Source order, list roles, and item metadata matter more than the visual column a card lands in.

5 min read

Responsive

Container breakpoints beat viewport assumptions

Use breakpoint maps when the component lives inside panels, sidebars, or resizable workspaces.

6 min read

Design

Composing card systems around unknown height

Small differences in copy, badges, and media ratios add up. Balanced placement keeps the page calm.

8 min read

When to use

  • Editorial cards with mixed copy length.
  • Cards with optional badges, metadata, or media.
  • Image grids where dimensions are known and visual balance matters.

Basic usage

import { MasonryBalanced } from 'masonix';

export function ArticleGrid({ articles }: { articles: Article[] }) {
  return (
    <MasonryBalanced
      items={articles}
      columns={{ 0: 1, 640: 2 }}
      gap={16}
      estimatedItemHeight={220}
      itemKey={(article) => article.id}
      render={({ data }) => <ArticleCard article={data} />}
    />
  );
}

Known heights

When item dimensions are already known, pass getItemHeight to skip the measurement wait.

Marfa, TX

Desert dawn

Seattle, WA

Glass house

Reykjavik, IS

Blue hour

Lisbon, PT

Courtyard

Brooklyn, NY

Studio wall

Kyoto, JP

Quiet arch

Busan, KR

Sea path

Portland, OR

Green room

<MasonryBalanced
  items={photos}
  columns={{ 0: 1, 560: 2, 860: 3 }}
  gap={14}
  defaultWidth={960}
  itemKey={(photo) => photo.id}
  getItemHeight={(photo, _index, columnWidth) =>
    Math.round(columnWidth * (photo.height / photo.width))
  }
  render={({ data, width }) => <PhotoCard photo={data} width={width} />}
/>

Common mistakes

  • Do not pass unstable keys; measured heights should follow item identity.
  • Do not use CSS string gaps; balanced placement needs numeric gap values.
  • Do not set estimatedItemHeight far from real card heights.

On this page