Xshell Pro
📖 Tutorial

Crafting Cascading Grid Layouts: The Zigzag Pattern with CSS Transform

Last updated: 2026-05-10 09:15:09 Intermediate
Complete guide
Follow along with this comprehensive guide

Overview

Most CSS grid layouts are orderly—rows and columns aligned like a checkerboard. But sometimes you want a layout that feels more dynamic, where items cascade diagonally like a waterfall. This is the zigzag layout. The key to achieving it lies in a clever use of the transform property within a two-column grid. In this guide, you’ll learn how to create a staggered, zigzag pattern that preserves tab order and is fully responsive. By the end, you’ll understand how transform: translateY(50%) can shift elements by half their own height, creating a rhythmic, flowing design.

Crafting Cascading Grid Layouts: The Zigzag Pattern with CSS Transform
Source: css-tricks.com

Prerequisites

Before you start, make sure you have:

  • Basic understanding of HTML and CSS (including selectors).
  • Familiarity with CSS Grid (e.g., display: grid, grid-template-columns).
  • Knowledge of how transform and translateY work.
  • A code editor and browser for testing.

No JavaScript is required—this technique is pure CSS.

Step-by-Step Instructions

1. Set Up the HTML Structure

Start with a simple wrapper and several child items. For this demo, we’ll use five items, but the pattern scales to any number.

<div class="wrapper">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
  <div class="item">4</div>
  <div class="item">5</div>
</div>

2. Apply a Global Reset and Create the Grid

Use the universal selector to set box-sizing: border-box on all elements. This ensures that any border or padding is included in the element’s total width and height. Without this, an item with height: 100px and a 2px border would be 104px tall, breaking the transform math.

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

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

.item {
  height: 100px;
  border: 2px solid;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: sans-serif;
}

Each item has a fixed height (100px). This is the only hardcoded value in the technique—it gives the transform a reference point. You can change it, but note that the shift will be relative to that height.

3. Shift Even Items Down by Half Their Height

The magic happens with this CSS rule:

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

This selects every even .item (second, fourth, sixth, etc.) and pushes it down by 50% of its own height. Because each item is 100px tall, 50% equals 50px. The result: even items sit halfway down the row, creating a zigzag.

Why this selector? You might be tempted to use .item:nth-of-type(even). That works when all children are the same element type (like div). But if you ever mix element types (e.g., a p tag inside the wrapper), nth-of-type counts each type separately, leading to unexpected matches. :nth-child(even of .item) is more precise because it only counts elements with the class .item.

4. Test and Observe the Zigzag

Open your page in a browser. The first column contains items 1, 3, 5; the second column contains items 2, 4. Item 2 is shifted down 50px, item 4 down 50px, creating the cascading effect. Tab through the items—order is preserved (1,2,3,4,5), unlike a flexbox column-wrap approach which would break tab flow.

5. Add Optional Polish

You can style items further: add background colors, padding, or even a subtle animation. For example:

.item:nth-child(even of .item) {
  background: #f0f0f0;
  transform: translateY(50%);
  transition: transform 0.3s ease;
}

.item:nth-child(even of .item):hover {
  transform: translateY(calc(50% - 5px)); /* shift up slightly on hover */
}

6. Making It Responsive

On mobile, you may want a single column. Wrap the grid in a media query:

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

That way the zigzag only appears on wider screens.

Common Mistakes

Forgetting Box-Sizing

If you don’t set box-sizing: border-box, the item’s actual height will include borders/padding. Then translateY(50%) will shift by 50% of the content height only, creating an uneven stagger. Always reset box-sizing first.

Using the Wrong Selector

Using .item:nth-of-type(even) works for plain div items, but fails if you nest other elements. Stick with :nth-child(even of .item) for reliability.

Not Accounting for the Grid Gap

The gap (16px in our example) is not affected by the transform. The push-down starts after the item’s normal position (including gap). That is fine—it continues the cascading look. But if you reduce the gap to 0, items will overlap vertically if the shift is large. Always keep a reasonable gap to avoid collisions.

Assuming Even Items Work with Odd Number of Items

If you have an odd number of items (like 5), the last item sits alone in the first column and is not shifted. That’s expected and looks fine. But if you expect a symmetrical zigzag, you need an even number of items. Plan your content accordingly.

Forgetting That Transform Does Not Affect Layout

transform: translateY(50%) visually moves the item, but its original grid space remains. This means the intrinsic height of the grid row does not change—so items in the second column may appear to overflow below the first. To compensate, you can add a bottom margin to the wrapper or set a min-height. Alternatively, use position: relative and top, but that also does not affect layout. The transform method is the cleanest for this use case.

Summary

You’ve learned how to create a zigzag layout using CSS Grid and the transform property. The core trick is to shift even items down by 50% of their height, creating a cascading effect without breaking tab order. Remember to set box-sizing: border-box, use the precise :nth-child(even of .item) selector, and plan for responsiveness. This technique is lightweight, widely supported, and perfect for portfolios, galleries, or any design that needs a rhythmic flow.