Mosmox UI
Current version
GitHubnpmjs.com
Foundations

Masonry

Responsive variable-height columns for galleries, editorial cards, and dense content collections

Masonry is a layout primitive for variable-height content. It uses CSS multi-column layout so cards flow into compact columns without forcing every row to share the height of its tallest item.

See the live Masonry showcase.

Basic usage

import { Masonry } from '@mosmox/ui';

<Masonry columns={3} gap={20} aria-label="Project cards">
  {projects.map((project) => (
    <article key={project.id}>
      <h2>{project.name}</h2>
      <p>{project.description}</p>
    </article>
  ))}
</Masonry>

Each child is placed in an item wrapper with break-inside: avoid, so a card does not split between columns. The DOM order remains the source order; CSS columns determine the visual column flow.

Responsive column counts

Pass a number for a fixed column count or an object for breakpoint-specific counts. Values cascade upward from xs.

<Masonry columns={{ xs: 1, sm: 2, md: 3, xl: 4 }} gap={16}>
  {cards.map((card) => <Card key={card.id}>{card.content}</Card>)}
</Masonry>
BreakpointMinimum viewport width
xs0px
sm576px
md768px
lg992px
xl1200px
xxl1600px

The count is clamped to the safe range of 1–12.

Automatic column width

Use minColumnWidth when the number of columns should be determined by the available width instead of explicit breakpoints.

<Masonry minColumnWidth={240} gap="1rem">
  {items.map((item) => <Card key={item.id}>{item.content}</Card>)}
</Masonry>

The browser creates as many columns as fit while keeping each column at least 240px wide.

Independent gaps and semantic elements

gap sets both axes. Use rowGap and columnGap when vertical and horizontal spacing need different values. as changes the root element and itemAs changes the generated item wrappers.

<Masonry
  as="ul"
  itemAs="li"
  columns={{ xs: 1, md: 2 }}
  rowGap={24}
  columnGap={16}
  aria-label="Stories"
>
  {stories.map((story) => (
    <article key={story.id}>
      <h2>{story.title}</h2>
    </article>
  ))}
</Masonry>

Use itemAs="li" when the root is ul or ol. itemClassName and itemStyle apply to every generated item wrapper.

API reference

PropTypeDefaultDescription
childrenReactNode—Variable-height content items.
columnsnumber or responsive column map3Fixed or responsive column count.
gapnumber or string16pxFallback for both row and column gaps.
rowGapnumber or stringgapVertical space between items.
columnGapnumber or stringgapHorizontal space between columns.
minColumnWidthnumber or string—Enables automatic columns with this minimum width.
asdiv, section, main, ul, or oldivSemantic root element.
itemAsdiv, article, or lidivElement used for each generated item wrapper.
itemClassNamestring—Class applied to every generated item wrapper.
itemStyleCSSProperties—Inline styles applied to every generated item wrapper.
className / stylestring / CSSProperties—Custom root styling and CSS custom properties.

The root accepts the remaining standard HTMLAttributes properties, including id, role, aria-, event handlers, and data- attributes.

Accessibility

Masonry is a layout primitive and does not add a landmark automatically. Add an appropriate aria-label when the collection represents a named region. Keep headings, links, and interactive controls semantic inside each item. Visual column order can differ from row-based reading expectations, so choose Masonry for card collections where source-order reading remains understandable.

When to use Masonry

Use Masonry for galleries, portfolios, editorial cards, media collections, and dashboards with naturally different content heights. Use Grid when strict row alignment, predictable keyboard movement, or table-like comparison is more important than compact packing.