How to Create a Staggered Zigzag CSS Grid Layout
Introduction
Most grid layouts sit in neat rows, perfectly aligned—like soldiers in formation. But sometimes you want something with more rhythm—a layout where items cascade diagonally, like water flowing down a waterfall. This is the zigzag layout. And building it requires a small trick that reveals something fascinating about how CSS transforms actually work. In this step-by-step guide, you'll learn to create this dynamic effect using CSS Grid and a simple transform: translateY() trick, while avoiding common pitfalls like broken tab order and brittle fixed heights.

What You Need
- HTML – a container with block-level child elements (e.g.,
<div>s) - CSS Grid – basic understanding of
display: gridand grid properties - Transform property – familiarity with
translateY() - Code editor – any text editor or IDE (VS Code, Sublime, etc.)
- Browser – modern browser (Chrome, Firefox, Edge) for testing
No JavaScript is required. All the magic happens in CSS.
Step 1: Set Up the HTML Structure
Start with a wrapper container and a few child items. For a two-column zigzag, you can use 5 or more items, but the pattern works with any even number. Here's a sample structure:
<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>
Keep it simple. The .item class will be used for styling and targeting. No extra markup needed.
Step 2: Apply Global Box-Sizing
Before you write any grid styles, add a global reset for box-sizing. This ensures that borders and padding are included in the element's total width and height. Without this, your items might not be exactly 100px tall (if you set a fixed height), which will mess up the transform offset later.
*, *::before, *::after {
box-sizing: border-box;
}
This single rule prevents unexpected dimensions.
Step 3: Create the Two-Column Grid
Turn the wrapper into a grid container with two equal columns. Set a max-width for readability and center it.
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
max-width: 800px;
margin: 0 auto;
}
The gap creates space between items. You can adjust it later. Now your items will sit side-by-side in two columns, one row after another.
Step 4: Style the Grid Items
Give each item a fixed height and a border (or background) so you can see the effect. Without a height, the transform won't have a reference.
.item {
height: 100px;
border: 2px solid #333;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
The flex properties are optional but help center the numbers inside each box.
Step 5: Shift Even Items Down with Transform
Now the key trick: target every even item that is a child of .wrapper and move it down by half its own height. Use :nth-child(even of .item) for precise class-based selection.
.wrapper .item:nth-child(even of .item) {
transform: translateY(50%);
}
Why translateY(50%)? Because 50% of the element's own height shifts it exactly halfway down. This creates the zigzag stagger: even items are offset, while odd items stay in place. The result is a cascading “waterfall” effect.
Note: :nth-of-type(even) might work in this demo, but it selects by tag name, not class. If you ever mix element types, you'll get unexpected matches. :nth-child(even of .item) is safer.
Step 6: Adjust Gap and Positioning for Perfect Stagger
You may notice the stagger is off by a few pixels due to the gap. By default, items start at the top of their cell. The gap applies between grid cells, not between the items' visual positions after transform. To fix this, remove the gap and use margins on items instead, or adjust the transform value slightly.
One reliable approach: set the grid gap to 0 and use margin-bottom on all items equal to the desired gap. Then the even items' transform will align correctly.
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0; /* remove grid gap */
max-width: 800px;
margin: 0 auto;
}
.item {
height: 100px;
border: 2px solid #333;
margin-bottom: 16px; /* replace gap */
/* ... other styles */
}
Now the translateY(50%) shifts the even items by exactly half their (content + border) height, creating a perfectly staggered layout.
Step 7: Add More Items and Test Responsiveness
The pattern works for any number of items. Add more to the wrapper and see the zigzag continue. Because the grid auto-places items, the stagger repeats every two items. For a longer cascade, you can increase the number of items.
Test on different screen sizes. The grid is responsive—columns will stay side-by-side unless you add a media query to stack them on small screens.
Tips for Success
- Tab order stays logical: Unlike flexbox with
flex-wrapand a fixed height, this grid method preserves natural left-to-right, top-to-bottom navigation. Items tab in order 1, 2, 3, 4, 5—not 1, 3, 5, 2, 4. - Avoid fixed heights if possible: The
translateY(50%)trick depends on a known height. If your content varies, consider usingtranslateY(calc(50% + some px))or define a consistentmin-height. - Use
overflow: hiddenon the wrapper: If items overflow due to the transform, clip the excess to keep the layout clean. - Combine with animations: Add CSS transitions to the transform property for a smooth entrance effect when items appear.
- Check browser support:
:nth-child(An+B of S)is supported in all modern browsers (Chrome 111+, Firefox 113+, Safari 15.4+). For older browsers, fall back to:nth-of-typeor use a different approach. - Experiment with negative transforms: To create a reverse stagger, use
translateY(-50%)on odd items.
With these steps, you now have a flexible, accessible zigzag layout using CSS Grid and a single transform—no JavaScript, no fragile heights, just clean code.
Related Articles
- Transforming Your Astro Workflow: A How-To for the Markdown Component
- GCC 16.1: What's New in the Latest GNU Compiler Collection Release
- JavaScript Temporal API Reaches Final Stage: End of Era for Moment.js
- Exploring CSS Color Palettes Beyond Tailwind: A Curated Collection
- Boost JavaScript Startup: Using V8 Explicit Compile Hints Step by Step
- Crafting Zigzag Patterns with CSS Grid and Transform
- React Native 0.80: Key Updates and What They Mean for Developers
- Decoding the Backend Architecture of a VK Video Downloader: Overcoming HLS and DOM Hurdles