Masonry Grid
A responsive masonry layout with animated reflow, cards gliding into place as the column count and the data change.
Items are absolutely positioned into whichever column is shortest, producing a staggered masonry flow. The column count changes with the container width, and every card glides into its new place when data is added, removed, or reordered.
npx shadcn@latest add https://webberui.com/r/masonry-grid.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<MasonryGrid />
Installation
npx shadcn@latest add https://webberui.com/r/masonry-grid.jsonOr, once registries are configured in components.json, install it as @webberui/masonry-grid.
Usage
import { MasonryGrid, type MasonryItem } from "@/components/ui/masonry-grid";
const items: MasonryItem[] = [
{ id: "a", height: 200, className: "bg-sky-500" },
{ id: "b", height: 140, className: "bg-rose-500" },
{ id: "c", height: 260, className: "bg-emerald-500" },
];
<MasonryGrid items={items} columns={{ 0: 1, 640: 2, 1024: 3 }} gap={16} />;columns can be a fixed number (for example 3) or a "container-width breakpoint map": the key is the minimum container width (px) and the value is the column count at or above that width. Breakpoints are decided by the container width rather than the viewport width, so the layout is still correct inside a sidebar or a popover.
Each item's height sets the card height while the width is derived from the column width; put your content in content, or apply a gradient background directly through className.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | MasonryItem[] | — | Grid item data |
columns | number | Record<number, number> | { 0: 1, 480: 2, 768: 3, 1024: 4 } | Fixed column count or a responsive breakpoint map |
gap | number | 16 | Gap between cards (px) |
duration | number | 0.5 | Duration of a single card's animation (seconds) |
stagger | number | 0.05 | Interval between cards on reveal (seconds) |
animateFrom | "bottom" | "top" | "left" | "right" | "center" | "bottom" | Direction the reveal starts from |
blurToFocus | boolean | true | Whether the reveal goes from blurred to sharp |
scaleOnHover | boolean | true | Whether a card scales up slightly on hover |
ease | [number, number, number, number] | [0.22, 1, 0.36, 1] | Easing curve of the animation |
MasonryItem
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier; the reflow animation tracks the same card by it |
height | number | Card height (px); the width is derived from the column width |
content | React.ReactNode | Card content; a placeholder panel renders when it is not supplied |
className | string | Styles appended to the card's frame |
Accessibility
- When the user has "reduce motion" enabled at the system level, cards land in place directly with no reveal or reflow animation
- The layout only renders after the container width has been measured on the client, avoiding a mismatch between server and client (hydration mismatch)
- A
ResizeObserverwatches the container width and disconnects automatically on unmount, so any size change reflows immediately