Masonry Layout
Pinterest-style variable-height grid using CSS columns, CSS grid subgrid, and a lightweight Alpine.js JS fallback.
Approach Comparison
Three viable techniques in this stack — each with a different trade-off profile.
| Technique | DOM order | JS needed | Image support | Best for |
|---|---|---|---|---|
| CSS columns | Top → bottom per column | None | After load (no reflow) | Text-heavy cards, blogs |
| CSS grid dense | Row-first | None | Fixed-height rows only | Known-height thumbnails |
| JS span masonry | Source order preserved | Alpine (tiny) | Yes — recalc on load | Image galleries, Pinterest-style |
1. CSS Columns Masonry
Uses the CSS columns property — the simplest approach, zero JS. Items flow top-to-bottom within each column. Add break-inside: avoid on each card to prevent mid-card column breaks. Caveat: reading order follows columns not rows (A→B→C down col 1, then D→E→F down col 2).
2 columns
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
3 columns — standard gallery density
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
Accessibility
Dark Mode
Iconography
Spacing Scale
Components
Tokens
4 columns — dense thumbnail grid
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
Accessibility
Dark Mode
Iconography
Spacing Scale
Components
Tokens
2. CSS Columns — Responsive Auto Width
columns: 16rem tells the browser "fit as many 16 rem columns as possible" — no breakpoints needed. Resize the window to watch it reflow from 1 → 2 → 3+ columns.
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
Accessibility
Dark Mode
Iconography
Spacing Scale
Components
Tokens
Equivalent CSS: columns: 16rem; column-gap: 0.75rem;
3. JS Masonry — Alpine + CSS Grid Row Span
Preserves source order (left→right across columns) and works with images. The grid uses 1 rem auto-rows; Alpine measures each card's natural height and sets grid-row-end: span N so it occupies the correct number of row tracks. Re-runs on window resize.
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
Accessibility
Dark Mode
Iconography
Spacing Scale
Components
Tokens
Source order is left→right, top→bottom — screen-reader and keyboard order matches visual order.
4. Gap Control
Gap between cards affects visual density and the feel of the layout. Small gaps suit editorial/news grids; larger gaps suit portfolio or lifestyle content.
Tight — 4px (news feed, dense catalogue)
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
Accessibility
Dark Mode
Iconography
Standard — 12px (blog, portfolio)
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
Accessibility
Dark Mode
Iconography
Airy — 24px (lifestyle, photography)
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
Accessibility
Dark Mode
Iconography
5. Content Density
Masonry shines when card heights vary significantly. These scenarios test the layout under realistic conditions.
Minimal — near-uniform height (masonry adds little value here)
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
Accessibility
Dark Mode
Iconography
High variance — 1 to 12 lines per card (masonry at its best)
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
Accessibility
Dark Mode
Iconography
Spacing Scale
Components
Tokens
6. Interactive Playground
Adjust technique, column count, and gap. The CSS snippet updates live.
Branding
Typography
UI Patterns
Motion
Grid Systems
Color Theory
Accessibility
Dark Mode
Iconography
7. Edge Cases
Scenarios that expose weaknesses — know them before choosing a technique.
Empty — zero items, container must not collapse
Single item — CSS columns: occupies only column 1
Only Card
With JS masonry + auto-fit minmax, a single item would expand to fill the full row instead.
Long unbreakable word — must not overflow card
Overflow Card
Supercalifragilisticexpialidocious-masonry-overflow-test-word
Fix: add overflow-wrap: break-word to cards. Without it the card widens and distorts the column.
Infinite scroll / dynamic insertion
CSS columns — new items append naturally; no action needed.
JS masonry — call layout() after each batch is inserted into the DOM
(use a MutationObserver or trigger from the data-fetch callback).
Wrap in requestAnimationFrame to ensure heights are settled before measuring.