Masonry
masonryDistributes items deterministically across responsive columns without hydration-order changes.
Usage
Basic usage
Input data source and renderItem, sort by source order round-robin, 3 columns of waterfall flow.
<Masonry
items={photos}
columns={3}
gap={16}
renderItem={(photo) => <img src={photo.url} alt={photo.alt} />}
/>Customize the number of columns
columns passes the number to fix the number of columns, and gap controls the inter-column and intra-column spacing (pixels).
<Masonry items={photos} columns={2} gap={16} renderItem={(p) => <Card {...p} />} />Responsive columns
columns passes {base, sm, md, lg}: SSR/the first frame uses base, and switches according to the window breakpoint after mounting.
<Masonry
items={photos}
columns={{ base: 1, sm: 2, md: 3, lg: 4 }}
gap={16}
renderItem={(photo) => <img src={photo.url} alt={photo.alt} />}
/>When to use
Use Masonry to arrange unequal-height cards—such as photos, activity items, or generated assets—into staggered columns while preserving SSR safety and source order. It distributes items deterministically rather than using native CSS columns, avoiding hydration mismatches and visual reordering. Use AspectRatio for one fixed-ratio item, or CSS Grid for a regular equal-height layout.
Import
import { Masonry } from "@hulianui/ui"Props
Masonry<T> generic component.
| Name | Type | Default | Description |
|---|---|---|---|
| items* | T[] | — | Source items, distributed round-robin in source order. |
| columns | number | { base?: number; sm?: number; md?: number; lg?: number } | 3 | Fixed count when numeric, or responsive counts by breakpoint. base is used for SSR and the first client frame; matchMedia selects a breakpoint after mount. |
| gap | number | 16 | Gap between columns and between items within each column (px). |
| className | string | — | The root container class name. |
Slots
| Slot | Type | Description |
|---|---|---|
| renderItem* | (item: T, index: number) => ReactNode | Renders one item; the returned node is wrapped in a column cell. |
Example
// Fixed 3 columns
<Masonry items={tiles} columns={3} gap={16} renderItem={(t) => <Tile tile={t} />} />// Responsive columns; base is used during SSR and on the first client frame
<Masonry
items={photos}
columns={{ base: 1, sm: 2, md: 3, lg: 4 }}
gap={16}
renderItem={(photo) => <img src={photo.url} alt={photo.alt} />}
/>Usage guidelines
- Responsive columns use `base` for SSR and the first client frame. After mounting,
matchMediaswitches to the current breakpoint count. This intentionally prevents hydration mismatches, so a wide viewport may briefly begin with thebasecount. - Round-robin does not fill the shortest column. The deterministic rule is
item[i] → column i % count, which preserves order and SSR output but can leave column heights uneven. Do not use Masonry when exact shortest-column packing is required. - The
sm,md, andlgbreakpoints are 640, 768, and 1024 px, matching Tailwind defaults. The largest matching breakpoint wins; otherwise the component falls back tobase.
Related
Layout · AdminLayout · ScrollArea · Viewport · Resizable · AspectRatio
Playground
<Masonry
items={photos}
columns={{ base: 1, sm: 2, md: 3, lg: 4 }}
gap={16}
renderItem={(photo) => <img src={photo.url} alt={photo.alt} />}
/>