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>| Breakpoint | Minimum viewport width |
|---|---|
| xs | 0px |
| sm | 576px |
| md | 768px |
| lg | 992px |
| xl | 1200px |
| xxl | 1600px |
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
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Variable-height content items. |
| columns | number or responsive column map | 3 | Fixed or responsive column count. |
| gap | number or string | 16px | Fallback for both row and column gaps. |
| rowGap | number or string | gap | Vertical space between items. |
| columnGap | number or string | gap | Horizontal space between columns. |
| minColumnWidth | number or string | — | Enables automatic columns with this minimum width. |
| as | div, section, main, ul, or ol | div | Semantic root element. |
| itemAs | div, article, or li | div | Element used for each generated item wrapper. |
| itemClassName | string | — | Class applied to every generated item wrapper. |
| itemStyle | CSSProperties | — | Inline styles applied to every generated item wrapper. |
| className / style | string / 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.