12338
Web Development

Create a Dynamic Zigzag Layout with CSS Grid and TranslateY

Posted by u/Lolpro Lab · 2026-05-06 19:07:41

The Challenge of Non-Linear Layouts

Most grid-based designs rely on straight rows and columns, creating a structured, predictable rhythm. However, sometimes you need a layout that feels more organic—like items cascading diagonally in a waterfall pattern. This zigzag effect adds visual interest and can help break the monotony of standard designs.

Create a Dynamic Zigzag Layout with CSS Grid and TranslateY
Source: css-tricks.com

Building such a layout might seem tricky at first, but with a clever combination of CSS Grid and the translateY() transform, you can achieve a smooth, staggered arrangement without breaking accessibility or logical order. Let's explore the strategy and implementation step by step.

Initial Flexbox Approach and Its Pitfalls

One might first consider using Flexbox with flex-direction: column and flex-wrap: wrap. This would allow items to flow down a column and then wrap into a second column. While conceptually simple, this method introduces two significant problems:

  • Fixed height requirement: You must define a fixed height for the container (e.g., 500px) to make wrapping work. This makes the layout brittle and unresponsive.
  • Broken tab order: Items flow down the first column (1, 2, 3) then jump to the second (4, 5, 6). This disrupts the natural reading and keyboard navigation order—hardly a waterfall effect.

The CSS Grid approach, which we'll build next, avoids the tab order issue entirely and only requires a single hardcoded value (the translation amount), which is far more manageable.

The Grid Strategy

The core idea is straightforward:

  1. Create a two-column grid with items placed side by side.
  2. Select every item in the second column (the even-numbered ones).
  3. Shift them down by half of their own height to create the staggered zigzag.

This translation is where the magic happens. Let's implement it.

Setting Up the Grid

Start with a wrapper containing five items. The markup is simple:

<div class="wrapper">
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
  <div class="item"></div>
</div>

Now apply the basic CSS:

*,
*::before,
*::after {
  box-sizing: border-box;
}

.wrapper {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  max-width: 800px;
  margin: 0 auto;
}

.item {
  height: 100px;
  border: 2px solid;
}

Notice the global box-sizing: border-box. Without it, the border adds extra height, making items taller than 100px. That would break the exact 50% translation we need later.

Applying the Vertical Shift

Now select every even item (the second column) and translate it downward by half its height:

.item:nth-child(even of .item) {
  transform: translateY(50%);
}

Why use :nth-child(even of .item) instead of the more common :nth-of-type(even)? In this demo all children are the same element type, so both would work. But :nth-of-type selects by tag name, not by class. If you ever mix element types (e.g., a heading inside the wrapper), it will match unexpectedly. The of .item syntax is more precise—it only considers elements that match the class .item.

The result is a perfect zigzag: items in the first column stay at their natural top position, while those in the second column drop by 50% of their own height, creating a cascading effect.

Why This Works

The translateY(50%) operates relative to the element's own height. Because all items share the same height (thanks to the fixed 100px value and box-sizing), the shift is consistent. Grid placement already puts even items in the second column automatically, so no extra positioning is needed.

This method preserves the natural tab order: items are read left-to-right, top-to-bottom (1,2,3,4,5) because the DOM order remains unchanged. The visual displacement does not affect the logical flow—a key advantage over the Flexbox wrapping approach.

Considerations and Alternatives

Responsive Adaptations

If you switch to a single column on small screens, disable the transform and adjust the grid to one column:

@media (max-width: 600px) {
  .wrapper {
    grid-template-columns: 1fr;
  }
  .item:nth-child(even of .item) {
    transform: none;
  }
}

Varying Item Heights

If your items have different heights, the 50% shift will create uneven drops. You can still achieve a zigzag, but the pattern becomes less predictable. In that case, consider using a fixed translation value (e.g., translateY(30px)) or JavaScript to calculate heights dynamically.

Browser Support

The :nth-child( even of .item ) syntax is supported in all modern browsers (Chrome, Firefox, Safari, Edge). For older browsers, fall back to .item:nth-of-type(even) if the element types are consistent.

Conclusion

By combining CSS Grid with a well-placed transform, you can create an elegant zigzag layout that maintains proper DOM order and responsiveness. The trick lies in understanding how translateY(50%) references the element's own height, and how :nth-child with a class filter provides precise selection. This technique is a small but powerful addition to any front-end developer’s toolkit—perfect for adding rhythm to otherwise static designs.