15 CSS Flexbox Layouts 04 / 15

CSS Flexbox Masonry-Style Layout

A multi-column masonry-style card waterfall using <code>flex-direction: column</code> and <code>flex-wrap: wrap</code> with a fixed container height — no JavaScript required.

Pure CSS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="fl-04">
  <div class="fl-04__header">
    <h2 class="fl-04__title">CSS Flexbox Masonry-Style Layout</h2>
    <div class="fl-04__badge">flex-direction: column + flex-wrap</div>
  </div>

  <div class="fl-04__masonry">

    <!-- Col items — varied heights via padding/content -->
    <div class="fl-04__item">
      <div class="fl-04__item-visual" style="height:100px; background:linear-gradient(135deg,#1a0a2e,#4a1a8c)">
        <div class="fl-04__shape" style="width:52px;height:52px;background:#e040fb;opacity:0.7"></div>
      </div>
      <div class="fl-04__item-body">
        <div class="fl-04__item-cat">Layout</div>
        <div class="fl-04__item-title">Column-Wrap Masonry</div>
        <div class="fl-04__item-text">Set <code>flex-direction: column</code> and <code>flex-wrap: wrap</code> with a fixed container height to make items flow into natural columns.</div>
        <div class="fl-04__item-tags">
          <div class="fl-04__tag">flex-wrap</div>
          <div class="fl-04__tag">column</div>
        </div>
      </div>
    </div>

    <div class="fl-04__item">
      <div class="fl-04__item-visual" style="height:70px; background:linear-gradient(135deg,#001a33,#004080)">
        <div class="fl-04__shape" style="width:44px;height:44px;background:#06b6d4;opacity:0.8;border-radius:30% 70% 70% 30%"></div>
      </div>
      <div class="fl-04__item-body">
        <div class="fl-04__item-cat">Sizing</div>
        <div class="fl-04__item-title">Item Width</div>
        <div class="fl-04__item-text">Items need a fixed <code>width</code> (e.g. 33%) — not <code>flex</code> shorthand — so each column stays the same width.</div>
      </div>
    </div>

    <div class="fl-04__item">
      <div class="fl-04__item-visual" style="height:130px; background:linear-gradient(135deg,#1a0500,#4d1a00)">
        <div class="fl-04__shape" style="width:68px;height:68px;background:#f59e0b;opacity:0.8;border-radius:60% 40% 30% 70%"></div>
      </div>
      <div class="fl-04__item-body">
        <div class="fl-04__item-cat">Container</div>
        <div class="fl-04__item-title">The Height Constraint Controls Column Count</div>
        <div class="fl-04__item-text">The container's fixed height determines when items wrap to a new column. Taller height = fewer columns. This is the key insight for this technique.</div>
        <div class="fl-04__item-tags">
          <div class="fl-04__tag">height</div>
          <div class="fl-04__tag">wrap boundary</div>
        </div>
      </div>
    </div>

    <div class="fl-04__item">
      <div class="fl-04__item-visual" style="height:90px; background:linear-gradient(135deg,#001a12,#004d35)">
        <div class="fl-04__shape" style="width:56px;height:56px;background:#10b981;opacity:0.8;border-radius:40% 60% 55% 45%"></div>
      </div>
      <div class="fl-04__item-body">
        <div class="fl-04__item-cat">Gap</div>
        <div class="fl-04__item-title">Gutters with gap</div>
        <div class="fl-04__item-text">The <code>gap</code> shorthand sets row and column gutters uniformly without margin edge-cases.</div>
      </div>
    </div>

    <div class="fl-04__item">
      <div class="fl-04__item-visual" style="height:80px; background:linear-gradient(135deg,#1a0a1a,#4a1a4a)">
        <div class="fl-04__shape" style="width:48px;height:48px;background:#e040fb;opacity:0.7"></div>
      </div>
      <div class="fl-04__item-body">
        <div class="fl-04__item-cat">Caveat</div>
        <div class="fl-04__item-title">Reading Order Is Top-Down</div>
        <div class="fl-04__item-text">Items flow column-first, not row-first. For strict row order, use CSS Grid instead.</div>
      </div>
    </div>

    <div class="fl-04__item">
      <div class="fl-04__item-visual" style="height:110px; background:linear-gradient(135deg,#1a1000,#4d3300)">
        <div class="fl-04__shape" style="width:62px;height:62px;background:#f59e0b;opacity:0.7;border-radius:50% 50% 30% 70%"></div>
      </div>
      <div class="fl-04__item-body">
        <div class="fl-04__item-cat">Responsive</div>
        <div class="fl-04__item-title">Dynamic Column Count via Container Height</div>
        <div class="fl-04__item-text">Use a CSS custom property for container height and update it via JS or @media queries to control how many columns appear at each breakpoint.</div>
        <div class="fl-04__item-tags">
          <div class="fl-04__tag">responsive</div>
          <div class="fl-04__tag">custom props</div>
        </div>
      </div>
    </div>

  </div>
</div>
.fl-04, .fl-04 *, .fl-04 *::before, .fl-04 *::after {
  margin: 0; padding: 0; box-sizing: border-box;
}
.fl-04 ::selection { background: #e040fb; color: #fff; }

.fl-04 {
  --bg: #f4f0ff;
  --surface: #fff;
  --ink: #1a0a2e;
  --muted: #8a7fa8;
  --accent: #7c3aed;
  --accent2: #e040fb;
  --accent3: #06b6d4;
  --accent4: #f59e0b;
  --border: rgba(124,58,237,0.12);
  font-family: 'Space Grotesk', sans-serif;
  background: var(--bg);
  padding: 24px;
  border-radius: 16px;
  min-height: 500px;
}

.fl-04__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
}
.fl-04__title {
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--ink);
}
.fl-04__badge {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--accent);
  color: #fff;
  padding: 4px 10px;
  border-radius: 20px;
}

/*
  Flexbox masonry: flex-direction column + flex-wrap wrap
  Height of container controls how many items per column.
  Items flow top→bottom then start a new column.
  Defensive overflow:hidden prevents excess columns from bleeding past
  the parent width at narrow viewports — the masonry algorithm doesn't
  shrink column count on its own, it just keeps packing.
*/
.fl-04__masonry {
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  gap: 12px;
  height: 760px; /* controls column count at desktop width */
  overflow: hidden;
}

.fl-04__item {
  width: calc(33.33% - 8px);
  background: var(--surface);
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--border);
  break-inside: avoid;
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  transition: transform 0.2s, box-shadow 0.2s;
}
.fl-04__item:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 30px rgba(124,58,237,0.15);
}

.fl-04__item-visual {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}
.fl-04__item-visual-inner {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.fl-04__shape {
  border-radius: 50%;
  animation: fl-04-float 4s ease-in-out infinite;
}
@keyframes fl-04-float {
  0%,100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-6px) rotate(8deg); }
}
.fl-04__item:nth-child(3n+2) .fl-04__shape { animation-delay: -1.5s; }
.fl-04__item:nth-child(3n+3) .fl-04__shape { animation-delay: -3s; }

.fl-04__item-body {
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
}
.fl-04__item-cat {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.fl-04__item-title {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--ink);
  line-height: 1.3;
}
.fl-04__item-text {
  font-size: 0.77rem;
  color: var(--muted);
  line-height: 1.6;
}
.fl-04__item-tags {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin-top: 4px;
}
.fl-04__tag {
  font-size: 0.65rem;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 20px;
  background: rgba(124,58,237,0.1);
  color: var(--accent);
}

/* Responsive masonry. The flex-direction:column masonry algorithm
   doesn't auto-reduce column count when items get narrow — we have to
   adjust the container height + item width per breakpoint or items
   keep wrapping into more columns. */
@media (max-width: 760px) {
  .fl-04__masonry { height: 1100px; }
  .fl-04__item { width: calc(50% - 6px); }
}
@media (max-width: 520px) {
  .fl-04__masonry {
    flex-wrap: nowrap;
    height: auto;
    overflow: visible;
  }
  .fl-04__item { width: 100%; }
  .fl-04__header { flex-direction: column; align-items: flex-start; gap: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .fl-04__shape { animation: none; }
  .fl-04__item { transition: none; }
}

How this works

The container uses display: flex; flex-direction: column; flex-wrap: wrap with an explicit height. Cards flow top-to-bottom and wrap into new columns when they exceed the container height, creating a natural column waterfall. Each card has a flex-basis that varies by content, producing the staggered-height masonry look.

The key limitation is that the container must have a fixed height for wrapping to occur. Items are ordered top-to-bottom in each column, then left-to-right across columns — which differs from true masonry (left-to-right row order). For strict source-order masonry, CSS Multi-Column or JavaScript is needed.

Customize

  • Control column count by adjusting the container height — shorter height forces earlier wrapping into more columns.
  • Set item widths explicitly with width: calc(33.33% - 12px) on cards to lock a three-column layout regardless of content height.
  • Add a gap between columns by applying column-gap on the container (works alongside gap on newer browsers).
  • Force specific cards to span taller by setting a larger flex-basis (e.g. flex-basis: 320px) on the target element.
  • Change from column-order to row-order by switching to CSS Grid with grid-auto-flow: dense when strict reading order matters.

Watch out for

  • The container requires an explicit height for column wrapping to work — percentage heights only work if the parent also has an explicit height.
  • Source order in column-direction flex is column-first, not row-first, which can confuse screen readers and keyboard navigation order.
  • Adding items dynamically can cause column rebalancing that shifts existing cards; prefer JavaScript masonry libraries when items change at runtime.

Browser support

ChromeSafariFirefoxEdge
29+ 9+ 28+ 29+

Column-direction flex wrapping is universally supported; the visual result differs subtly in Safari due to line-sizing algorithm differences.

Search CodeFronts

Loading…