Responsive pyramidal grid with modern CSS layout logic

The “hacky” era of hexagon layouts is finally ending. For years, if a designer handed you a Responsive Pyramidal Grid made of hexagons, your first move was probably a heavy JavaScript library, or absolute offsets calculated inside a forEach loop. CSS has since turned into something closer to a logic engine, which is overdue.

Five years ago, a Clean Responsive Hexagon Grid meant negative margins stacked on top of fragile nth-child selectors. Now there is sibling-index() and corner-shape, so the geometry can come out of math instead. If your layout breaks every time the window resizes, the fix is in the CSS structure rather than in more JavaScript.

Why grid beats flexbox here

Flexbox is fine for a simple row, but a pyramid needs strict control over which column each item lands in. Give the grid an even number of columns, let every item span two units, and you get the half-step shift that hexagons need. auto-fit fills the container, and the auto-placement algorithm handles the rest.

.container {
  --s: 40px;  /* element size */
  --g: 5px;   /* gap */

  display: grid;
  grid-template-columns: repeat(auto-fit, var(--s) var(--s));
  justify-content: center;
  gap: var(--g);
}

.container > * {
  grid-column-end: span 2;
  aspect-ratio: cos(30deg);
  border-radius: 50% / 25%;
  corner-shape: bevel; /* Experimental: Defines the hexagon shape */
}

Calculating the pyramid with sibling-index

A Responsive Pyramidal Grid comes down to triangular numbers. Each row starts at a specific index: 1, 2, 4, 7, 11, and so on. Instead of writing 50 :nth-child selectors, the sibling-index() function works out the row and column for each item.

I have lost whole evenings to layout scripts that fired before the fonts loaded and collapsed the grid on me. Move that logic into the CSS engine and the entire category of bug goes away. Here is how the math looks once it becomes a custom property:

.container > * {
  /* Calculate the 'j' value (triangular root) */
  --_j: calc(sqrt(2 * sibling-index() - 1.75) - 0.5);
  --_d: mod(var(--_j), 1);
  
  /* If --_d is 0, we are at the start of a row in the pyramid */
  grid-column-start: if(style(--_d: 0): calc(var(--_n) - var(--_j)));
}

Handling the responsive fallback

Browser support is the catch. corner-shape: bevel is still experimental, so the pyramid needs a sane default sitting behind it. When the container gets too narrow to hold the shape, the grid should give up on the pyramid and fill the space like an ordinary grid.

max(0, var(--_n) - var(--_j)) clamps negative column values to 0, which the browser reads as invalid and falls back to its default auto-placement order. That is cleaner than writing a media query for every 10 pixels of screen width.

If this kind of grid work is eating your dev hours, I can take it off your plate. I have been working with WordPress since the 4.x days.

Writing layout as math

CSS is no longer a static list of declarations. With calc(), mod() and the upcoming if(), you are writing layout algorithms. In practice that means looking for the mathematical pattern in a design before you go hunting for the right property.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.