Skip to content
DocsLayout enginesMasonry, justified & grid

Layout engines

Three pure, O(n) algorithms. Each visits every image once and returns absolute positions.

Masonry (Pinterest)

Pinterest content is visually heterogeneous — every pin has a different aspect ratio. Masonry preserves each ratio and packs tightly by dropping the next image into the shortest column.

for (const image of images) {
  const renderHeight = (image.height / image.width) * columnWidth;
  const c = shortestColumn(heights);           // scan of ≤ 6 columns → O(1)
  place(image, x = c * (colW + gap), y = heights[c]);
  heights[c] += renderHeight + gap;
}
containerHeight = max(heights) - gap;

Incremental append. Infinite scroll continues from the prior column-height vector, so new pages cost O(delta) and existing tiles never move:

const state = computeMasonryState(firstPage, width, { columns, gap });
const next = appendMasonry(state, newImages); // O(newImages)

Column count is responsive: 2 / 3 / 4 / 6 at 640 / 1024 / 1536px.

Greedy row filling, then every image in a row is scaled to a shared height so the row width exactly equals the container — no gaps, no cropping, ratios preserved.

// For a row of k images with aspect ratios arᵢ:
rowHeight = (containerWidth - gap * (k - 1)) / Σ arᵢ;
widthᵢ    = arᵢ * rowHeight;       //  Σ widthᵢ + gaps === containerWidth

A row closes once scaling it to fill the width brings it to or under the target row height. The trailing row is filled only if it's close to target; otherwise it's left-aligned to avoid grotesquely large images.

Fixed grid (Airbnb listings)

Uniform aspect-ratio cells in perfectly aligned rows — the eye can compare listings. The cell ratio adapts by breakpoint:

BreakpointColumnsCell aspect
mobile11:1
tablet24:3
desktop33:2
ultrawide416:9

Complexity

EngineTimeMemory
MasonryO(n) · O(delta) appendO(n) positions
JustifiedO(n)O(n) positions
Fixed gridO(n)O(n) positions