In the earlier article, we constructed the basic hexagon grid. It was a responsive implementation with out the usage of media queries. The problem was to enhance a five-year outdated strategy utilizing trendy CSS.
Help is proscribed to Chrome solely as a result of this system makes use of lately launched options, together with corner-shape, sibling-index(), and unit division.
On this article, we are going to discover one other sort of grid: a pyramidal one. We’re nonetheless working with hexagon shapes, however a distinct group of the weather.
A demo price a thousand phrases:
For higher visualization, open the full-page view of the demo to see the pyramidal construction. On display screen resize, you get a responsive habits the place the underside half begins to behave equally to the grid we created within the earlier article!

Cool proper? All of this was made and not using a single media question, JavaScript, or a ton of hacky CSS. You possibly can chunk as many components as you need, and every little thing will modify completely.
Earlier than we begin, do your self a favor and browse the earlier article if you happen to haven’t already. I’ll skip a couple of issues I’ve already defined there, corresponding to how the shapes are created in addition to a couple of formulation I’ll reuse right here. Just like the earlier article, the implementation of the pyramidal grid is an enchancment of a five-year outdated strategy, so if you wish to make a comparability between 2021 and 2026, take a look at that older article as properly.
The Preliminary Configuration
This time, we are going to depend on CSS Grid as a substitute of Flexbox. With this construction, it’s simple to manage the position of things inside columns and rows fairly than adjusting margins.
.container {
--s: 40px; /* dimension */
--g: 5px; /* hole */
show: grid;
grid-template-columns: repeat(auto-fit, var(--s) var(--s));
justify-content: heart;
hole: var(--g);
}
.container > * {
grid-column-end: span 2;
aspect-ratio: cos(30deg);
border-radius: 50% / 25%;
corner-shape: bevel;
margin-bottom: calc((2*var(--s) + var(--g))/(-4*cos(30deg)));
}
I'm utilizing the basic repeated auto-fit to create as many columns because the free area permits. For the objects, it’s the identical code of the earlier article for creating hexagon shapes.
You wrote
var(--s)twice. Is {that a} typo?
It’s not! I would like my grid to all the time have a good variety of columns, the place every merchandise spans two columns (that’s why I'm utilizing grid-column-end: span 2). With this configuration, I can simply management the shifting between the completely different rows.

Above is a screenshot of DevTools exhibiting the grid construction. If, for instance, merchandise 2 spans columns 3 and 4, then merchandise 4 ought to span columns 2 and three, merchandise 5 ought to span columns 4 and 5, and so forth.
It’s the identical logic with the responsive half. Every first merchandise of each different row is shifted by one column and begins on the second column.

With this configuration, the scale of an merchandise will likely be equal to 2*var(--s) + var(--g). For that reason, the unfavourable backside margin is completely different from the earlier instance.
So, as a substitute of this:
margin-bottom: calc(var(--s)/(-4*cos(30deg)));
…I'm utilizing:
margin-bottom: calc((2*var(--s) + var(--g))/(-4*cos(30deg)));
Nothing fancy to date, however we have already got 80% of the code. Imagine it or not, we're just one property away from finishing the whole grid. All we have to do is about the grid-column-start of some components to have the proper placement and, as you'll have guessed, right here comes the trickiest half involving a fancy calculation.
The Pyramidal Grid
Let’s suppose the container is massive sufficient to comprise the pyramid with all the weather. In different phrases, we are going to ignore the responsive half for now. Let’s analyze the construction and attempt to establish the patterns:

Whatever the variety of objects, the construction is in some way static. The objects on the left (i.e., the primary merchandise of every row) are all the time the identical (1, 2, 4, 7, 11, and so forth). A trivial resolution is to focus on them utilizing the :nth-child() selector.
:nth-child(1) { grid-column-start: ?? }
:nth-child(2) { grid-column-start: ?? }
:nth-child(4) { grid-column-start: ?? }
:nth-child(7) { grid-column-start: ?? }
:nth-child(11) { grid-column-start: ?? }
/* and so on. */
The positions of all of them are linked. If merchandise 1 is positioned in column x, then merchandise 2 needs to be positioned in column x - 1, merchandise 4 in column x - 2, and so forth.
:nth-child(1) { grid-column-start: x - 0 } /* 0 is just not want however helpful to see the sample*/
:nth-child(2) { grid-column-start: x - 1 }
:nth-child(4) { grid-column-start: x - 2 }
:nth-child(7) { grid-column-start: x - 3 }
:nth-child(11) { grid-column-start: x - 4 }
/* and so on. */
Merchandise 1 is logically positioned within the center, so if our grid comprises N columns, then x is the same as N/2:
:nth-child(1) { grid-column-start: N/2 - 0 }
:nth-child(2) { grid-column-start: N/2 - 1 }
:nth-child(4) { grid-column-start: N/2 - 2 }
:nth-child(7) { grid-column-start: N/2 - 3 }
:nth-child(11){ grid-column-start: N/2 - 4 }
And since every merchandise spans two columns, N/2 will also be seen because the variety of objects that may match inside the container. So, let’s replace our logic and think about N to be the variety of objects as a substitute of the variety of columns.
:nth-child(1) { grid-column-start: N - 0 }
:nth-child(2) { grid-column-start: N - 1 }
:nth-child(4) { grid-column-start: N - 2 }
:nth-child(7) { grid-column-start: N - 3 }
:nth-child(11){ grid-column-start: N - 4 }
/* and so on. */
To calculate the variety of objects, I'll use the identical method as within the earlier article:
N = spherical(down, (container_size + hole)/ (item_size + hole));
The one distinction is that the scale of an merchandise is now not var(--s)however 2*var(--s) + var(--g), which provides us the next CSS:
.container {
--s: 40px; /* dimension */
--g: 5px; /* hole */
container-type: inline-size; /* we make it a container to make use of 100cqw */
}
.container > * {
--_n: spherical(down,(100cqw + var(--g))/(2*(var(--s) + var(--g))));
}
.container > *:nth-child(1) { grid-column-start: calc(var(--_n) - 0) }
.container > *:nth-child(2) { grid-column-start: calc(var(--_n) - 1) }
.container > *:nth-child(4) { grid-column-start: calc(var(--_n) - 2) }
.container > *:nth-child(7) { grid-column-start: calc(var(--_n) - 3) }
.container > *:nth-child(11){ grid-column-start: calc(var(--_n) - 4) }
/* and so on. */
It really works! We've got our pyramidal construction. It’s not but responsive, however we are going to get there. By the way in which, in case your purpose is to construct such a construction with a hard and fast variety of objects, and also you don’t want responsive habits, then the above is ideal and also you’re executed!
How come all of the objects are appropriately positioned? We solely outlined the column for a couple of objects, and we didn’t specify any row!
That’s the facility of the auto-placement algorithm of CSS Grid. While you outline the column for an merchandise, the subsequent one will likely be robotically positioned after it! We don’t must manually specify a bunch of columns and rows for all of the objects.
Bettering the Implementation
You don’t like these verbose :nth-child() selectors, proper? Me too, so let’s take away them and have a greater implementation. Such a pyramid is well-known within the math world, and we've got one thing known as a triangular quantity that I'm going to make use of. Don’t fear, I can't begin a math course, so right here is the method I will likely be utilizing:
j*(j + 1)/2 + 1 = index
…the place j is a optimistic integer (zero included).
In concept, all of the :nth-child may be generated utilizing the next pseudo code:
for(j = 0; j< ?? ;j++) {
:nth-child(j*(j + 1)/2 + 1) { grid-column-start: N - j }
}
We don’t have loops in CSS, so I'll observe the identical logic I did within the earlier article (which I hope you learn, in any other case you're going to get a bit misplaced). I specific j utilizing the index. I solved the earlier method, which is a quadratic equation, however I'm certain you don’t wish to get into all that math.
j = sqrt(2*index - 1.75) - .5
We are able to get the index utilizing the sibling-index() perform. The logic is to check for every merchandise if sqrt(2*index - 1.75) - .5 is a optimistic integer.
.container {
--s: 40px; /* dimension */
--g: 5px; /* hole */
container-type: inline-size; /* we make it a container to make use of 100cqw */
}
.container > * {
--_n: spherical(down,(100cqw + var(--g))/(2*(var(--s) + var(--g))));
--_j: calc(sqrt(2*sibling-index() - 1.75) - .5);
--_d: mod(var(--_j),1);
grid-column-start: if(fashion(--_d: 0): calc(var(--_n) - var(--_j)););
}
When the --_d variable is the same as 0, it implies that --_j is an integer; and when that’s the case I set the column to N - j. I don’t want to check if --_j is optimistic as a result of it’s all the time optimistic. The smallest index worth is 1, so the smallest worth of --_j is 0.
Tada! We changed all of the :nth-child() selectors with three traces of CSS that cowl any variety of objects. Now let’s make it responsive!
The Responsive Habits
Again in my 2021 article, I switched between the pyramidal grid and the basic grid primarily based on display screen dimension. I'll do one thing completely different this time. I'll maintain constructing the pyramid till it’s now not doable, and from there, it should flip into the basic grid.

Objects 1 to twenty-eight kind the pyramid. After that, we get the identical basic grid we constructed within the earlier article. We have to goal the primary objects of some rows (29, 42, and so on.) and shift them. We're not going to set a margin on the left this time, however we do must set their grid-column-start worth to 2.
As regular, we establish the method of the objects, specific it utilizing the index, after which take a look at if the result's a optimistic integer or not:
N*i + (N - 1)*(i - 1) + 1 + N*(N - 1)/2 = index
So:
i = (index - 2 + N*(3 - N)/2)/(2*N - 1)
When i is a optimistic integer (zero excluded), we set the column begin to 2.
.container {
--s: 40px; /* dimension */
--g: 5px; /* hole */
container-type: inline-size; /* we make it a container to make use of 100cqw */
}
.container > * {
--_n: spherical(down,(100cqw + var(--g))/(2*(var(--s) + var(--g))));
/* code for the pyramidal grid */
--_j: calc(sqrt(2*sibling-index() - 1.75) - .5);
--_d: mod(var(--_j),1);
grid-column-start: if(fashion(--_d: 0): calc(var(--_n) - var(--_j)););
/* code for the responsive grid */
--_i: calc((sibling-index() - 2 + (var(--_n)*(3 - var(--_n)))/2)/(2*var(--_n) - 1));
--_c: mod(var(--_i),1);
grid-column-start: if(fashion((--_i > 0) and (--_c: 0)): 2;);
}
Not like the --_j variable, I want to check if --_i is a optimistic worth, as it may be unfavourable for some index values. For that reason, I've an additional situation in comparison with the primary one.
However wait! That’s no good in any respect. We're declaring grid-column-start twice, so solely one in all them will get used. We must always have just one declaration, and for that, we are able to mix each circumstances utilizing a single if() assertion:
grid-column-start:
if(
fashion((--_i > 0) and (--_c: 0)): 2; /* first situation */
fashion(--_d: 0): calc(var(--_n) - var(--_j)); /* second situation */
);
If the primary situation is true (the responsive grid), we set the worth to 2; else if the second situation is true (the pyramidal grid), we set the worth to calc(var(--_n) - var(--_j)); else we do nothing.
Why that exact order?
As a result of the responsive grid ought to have the next precedence. Verify the determine beneath:

Merchandise 29 is a part of the pyramidal grid because it’s the primary merchandise in its row. Which means that the pyramidal situation will all the time be true for that merchandise. However when the grid turns into responsive, that merchandise turns into a part of the responsive grid, and the opposite situation can also be true. When each circumstances are true, the responsive situation one ought to win; that’s why it’s the primary situation we take a look at.
Let’s see this in play:
Oops! The pyramid appears to be like good, however after that, issues get messy.
To grasp what is going on, let’s look particularly at merchandise 37. If you happen to test the earlier determine, you'll discover it’s a part of the pyramidal construction. So, even when the grid turns into responsive, its situation remains to be true and it will get a column worth from the method calc(var(--_n) - var(--_j)) which isn't good as a result of we wish to maintain its default worth for auto-placement. That’s the case for a lot of objects, so we have to repair them.
To seek out the repair, let’s see how the values within the pyramid behave. All of them observe the method N - j, the place j is a optimistic integer. If, for instance, N is the same as 10 we get:
10, 9, 8, 7, ... ,0, -1 , -2
At sure factors, the values change into unfavourable, and since unfavourable values are legitimate, these objects will likely be randomly positioned, disrupting the grid. We have to make sure the unfavourable values are ignored, and the default worth is used as a substitute.
We use the next to maintain solely the optimistic worth and rework all of the unfavourable ones into zeroes:
max(0, var(--_n) - var(--_j))
We set 0 at least boundary (extra on that right here) and the values change into:
10, 9, 8, 7, ... , 0, 0, 0, 0
We both get a optimistic worth for the column or we get 0.
However you stated the worth needs to be the default one and never
0.
Sure, however 0 is an invalid worth for grid-column-start, so utilizing 0 means the browser will ignore it and fall again to the default worth!
Our new code is:
grid-column-start:
if(
fashion((--_i > 0) and (--_c: 0)): 2; /* first situation */
fashion(--_d: 0): max(0,var(--_n) - var(--_j)); /* second situation */
);
And it really works!
You possibly can add as many objects as you need, resize the display screen, and every little thing will match completely!
Extra Examples
Sufficient code and math! Let’s take pleasure in extra variations utilizing completely different shapes. I’ll allow you to dissect the code as homework.
Rhombus grid
You'll discover a barely completely different strategy for setting the hole between the weather within the subsequent three demos.
Octagon grid
Circle grid
And the opposite hexagon grid:
Conclusion
Do you keep in mind once I advised you that we had been one property away from finishing the grid? That one property (grid-column-start) took us actually the entire article to debate! This demonstrates that CSS has developed and requires a brand new mindset to work with. CSS is now not a language the place you merely set static values such shade: crimson, margin: 10px, show: flex, and so on.
Now we are able to outline dynamic behaviors by means of complicated calculations. It’s an entire means of pondering, discovering formulation, defining variables, creating circumstances, and so forth. That’s not one thing new since I used to be in a position to do the identical in 2021. Nevertheless, we now have stronger options that permit us to have much less hacky code and extra versatile implementations.









