Media

Masonry Grid

A pure-CSS waterfall grid that stacks children into responsive columns without measuring layout.

Preview in your theme

Loading preview…

import * as React from "react"
import { cn } from "@/lib/utils"

type ColumnCount = 1 | 2 | 3 | 4

export interface MasonryGridColumns {
  base?: ColumnCount
  sm?: ColumnCount
  lg?: ColumnCount
}

export interface MasonryGridProps extends React.HTMLAttributes<HTMLDivElement> {
  /** Column count per breakpoint. Defaults to a 1 → 2 → 3 ramp. */
  columns?: MasonryGridColumns

Installation

npx shadcn@latest add https://ui.zyeon.ai/r/masonry-grid.json

Prompt

Build a React + TypeScript + Tailwind "MasonryGrid" layout container. No
external dependencies beyond Tailwind and the shared cn() class-merge helper.

Contract
- Named export `MasonryGrid`, typed with `MasonryGridProps`:
  - `columns?: { base?: 1|2|3|4; sm?: 1|2|3|4; lg?: 1|2|3|4 }` — defaults to
    `{ base: 1, sm: 2, lg: 3 }`.
  - `gap?: number` — px, default `16`.
  - `children`, `className`, plus the rest of native `div` props spread onto
    the root element.

Behavior
- Wrap each child in its own `break-inside-avoid` div (via `React.Children.map`)
  so CSS multi-column layout never splits one item across a column break.
- Layout is pure CSS — no measuring, no ResizeObserver, no reflow logic in
  JS. This stays a Server Component: no "use client", no hooks, no events.
- Item order follows column-fill order (top-to-bottom down one column, then
  the next), not row-major reading order — flag this to callers who need
  strict left-to-right sequence, they should reach for something else.
- `gap` drives a single CSS custom property (`--masonry-gap`) applied to both
  the container's `column-gap` and every wrapper's `margin-bottom`, so
  horizontal and vertical spacing stay in sync from one number.

Rendering & styling
- Column count per breakpoint resolves through three fixed lookup objects
  (base/sm/lg) mapping 1|2|3|4 to literal Tailwind classes ("columns-2",
  "sm:columns-3", …) — never build class names by string interpolation
  (`columns-${n}`), or Tailwind's build-time class scanner won't find them.
- `cn()` merges the resolved column classes with the consumer's `className`.
- No visual chrome of its own beyond the layout primitives — it only
  arranges whatever children are passed in, so it carries no color tokens.

Customization levers
- Column counts per breakpoint (`columns.base/sm/lg`, 1–4) — tune density.
- `gap` — spacing between both columns and stacked items.
- Extend to more breakpoint tiers (e.g. `md`, `xl`) by adding entries to the
  same three lookup tables, if a project needs finer-grained control.
- Pair with an image lightbox (wrap each `<img>` child in a click handler) or
  a stagger-in entrance animation on the children — MasonryGrid itself stays
  layout-only and doesn't own those behaviors.

Concepts

  • Column-fill order — CSS columns lays items down the first column before wrapping to the next, so masonry order is column-major, not left-to-right reading order.
  • break-inside-avoid wrapper — every child is wrapped in its own div carrying break-inside-avoid so an item is never split across a column break.
  • Static column class mapcolumns-1..4 per breakpoint come from a fixed lookup object, not a template string like columns-${n}, so Tailwind's build-time scanner can see every class that might render.
  • CSS var gap — one --masonry-gap custom property drives both column-gap (between columns) and each wrapper's margin-bottom (between stacked items), keeping spacing in sync from a single gap prop.
  • No measuring, no JS — pure CSS layout, no ResizeObserver, no height measurement, no "use client" — that's what keeps this a zero-JS Server Component and why it's a poor fit for strict visual ordering (reach for a JS masonry library instead).

On This Page