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. That is the zigzag layout. And building it requires a small trick that reveals something fascinating about how CSS transforms actually work. Each approach offers a unique visual effect and solves different layout challenges.

1. The Classic Two-Column Zigzag
The most straightforward zigzag CSS layout uses a two-column grid and shifts every even item down by half its own height. This creates an instant diagonal cascade that feels dynamic without sacrificing readability.
The Grid Setup
Start with a wrapper and five items. The HTML 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>
Apply a global box-sizing: border-box so that borders do not inflate the element’s height. Without it, a 100px tall item with a 2px border becomes 104px tall, breaking the precise shift calculation.
*, *: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;
}
The Shift
Now select every even item and translate it down by 50% of its own height:
.item:nth-child(even of the even items with a class filter:.item:nth-child(even of.item) {
transform: translateY(50%);
}
Why use :nth-child(even of.item) instead of :nth-of-type(even)? The latter matches by tag name, not class. If you mix different element types inside the wrapper, :nth-of-type will count incorrectly. The class‑filtered version is more precise and works in all modern browsers.
The zigzag emerges immediately. Each even item sits halfway down the height of its odd counterpart, creating a staggered effect.
Understanding Transform Percentages
Percentages in transforms work differently than anywhere else in CSS. In layout properties like width or margin, a percentage refers to the parent container. But in a transform, percentages refer to the element itself. So translateY(50%) moves the element down by half its own computed height, not half the available space.
The browser applies transforms after layout in the element’s own coordinate space. That means the element’s original position and size are fully calculated before the transform is applied. This is why scale(2) grows outward from the element’s center — it’s all relative to the element itself.
The Gap Problem
The result looks close, but it is not quite right. The gap between the first and second items is 16px, but the vertical distance between the second and third items is larger because the second item is shifted down by 50% of its height (50px) plus the gap. This creates an uneven rhythm.
Here is the fix: translate by calc(50% + var(--gap)/2). First, define a custom property for the gap:
.wrapper {
--gap: 16px;
gap: var(--gap);
}.item:nth-child(even of.item) {
transform: translateY(calc(50% + var(--gap)/2));
}
Now the vertical spacing between rows remains consistent. The zigzag stays proportional no matter how tall the items are, because the transform percentage always refers to the element’s own height.
2. The Three-Column Zigzag with Alternating Offsets
If you need a more complex staggered pattern, try a three-column grid with alternating shifts. This layout feels like a staircase rather than a simple zigzag.
The Grid Plan
Create a three-column grid. Select every second item (columns 2 and 3) and shift them by different amounts. For example, shift items in column 2 down by 25% and items in column 3 down by 50%.
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
max-width: 900px;
margin: 0 auto;
}.item:nth-child(3n+2) {
transform: translateY(25%);
}.item:nth-child(3n+3) {
transform: translateY(50%);
}
This creates a cascading effect where each column drops lower than the previous one. The pattern repeats every three items. Adjust the percentages to control the steepness of the cascade.
Why This Works
The same transform‑percentage rule applies. Since each item has the same height (100px in this example), the shifts are predictable. If items have variable heights, the layout adapts automatically because the percentage is always relative to the element itself.
3. The Diagonal Zigzag with Grid Placement
Instead of relying solely on nth-child selectors, you can explicitly place items in grid cells and then apply transforms to create a diagonal zigzag. This gives you fine control over which items shift and by how much.
The Grid Plan
Use a fixed number of columns and rows. Place items manually using grid-column and grid-row. Then apply transforms to create the zigzag.
.wrapper {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 100px);
gap: 10px;
max-width: 800px;
margin: 0 auto;
}.item:nth-child(1) { grid-column: 1; grid-row: 1; }.item:nth-child(2) { grid-column: 2; grid-row: 2; transform: translateY(-25%); }.item:nth-child(3) { grid-column: 3; grid-row: 1; transform: translateY(25%); }.item:nth-child(4) { grid-column: 4; grid-row: 2; transform: translateY(-25%); }.item:nth-child(5) { grid-column: 1; grid-row: 3; }.item:nth-child(6) { grid-column: 2; grid-row: 3; }
This approach is more verbose but gives you absolute control over the visual rhythm. The negative translate values pull items upward, creating a sawtooth pattern. Combine positive and negative shifts for a true zigzag.
You may also enjoy reading: Utah Tech vs Arizona: Step-by-Step Breakdown of 93-67 Win.
Practical Use Case
This layout works well for showcasing product features in a timeline or for displaying team member cards with alternating heights. Because you control placement, you can ensure the tab order follows the visual order, which is important for accessibility.
4. The Masonry‑Style Zigzag with Dense Packing
CSS Grid’s grid-auto-flow: dense can be combined with transforms to create a zigzag layout that fills gaps automatically. This is similar to a masonry grid but with a staggered appearance.
The Grid Plan
Use a two‑column grid with grid-auto-flow: dense. Items have different heights. Apply a transform to every even item to shift it down by a fixed amount (e.g., 50px) rather than a percentage, because the heights vary.
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
grid-auto-flow: dense;
max-width: 600px;
margin: 0 auto;
}.item {
border: 2px solid;
}.item:nth-child(even of.item) {
transform: translateY(50px);
}
Because grid-auto-flow: dense fills holes, the zigzag pattern may shift items to different columns to minimize whitespace. The transform still applies to the even items in the source order, but their visual position may change. This creates an organic, magazine‑like layout.
Caveat
The dense packing algorithm prioritizes filling algorithm can override your intended stagger. If you need strict zigzag order, avoid dense and stick with the classic approach. But for a more playful, unpredictable look, this is a great option.
5. The Rotated Zigzag with Skew and Scale
For a truly distinctive zigzag CSS layout, combine translateY with rotate and skew transforms. This creates a diagonal ribbon effect that feels dynamic and modern.
The Grid Plan
Use a single‑row grid with multiple columns, or a two‑column grid with multiple rows. Apply a combination of transforms to each item to create a continuous zigzag line.
.wrapper {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 4px;
max-width: 1000px;
margin: 0 auto;
}.item {
height: 150px;
border: 2px solid;
transform-origin: left center;
}.item:nth-child(odd of.item) {
transform: translateY(20px) rotate(2deg) skewX(-3deg);
}.item:nth-child(even of.item) {
transform: translateY(-20px) rotate(-2deg) skewX(3deg);
}
This pattern creates a wavy, zigzagging line across the page. The skewX tilts each item slightly, while the rotate adds a gentle twist. The result is a playful, hand‑drawn appearance.
Accessibility Note
Because transforms do not affect the layout flow, items may overlap visually if the shifts are too large. Always test with real content and ensure clickable elements remain accessible. Use z-index if items overlap and you need to control stacking.
Bringing It All Together
Each of these five methods demonstrates the power of CSS Grid combined with the transform property to create zigzag CSS layouts. The key takeaway is that transform percentages refer to the element itself, not the parent. This small distinction unlocks endless creative possibilities.
Remember to always include box-sizing: border-box to keep height calculations accurate. Use the :nth-child(even of.item) selector for precise targeting, and fix gap issues with calc(). Whether you are building a timeline, a product showcase, or a playful portfolio, these zigzag techniques will add visual interest without sacrificing structure.
Experiment with different column counts, shift amounts, and transform combinations. The zigzag is not just a pattern — it is a reminder that CSS is full of hidden power when you understand how the browser applies transforms after layout.






