A beehive honeycomb covered in bees and honey

Hexagons are a pain in the ass

🚧

Whoa there!

This post contains code that's more than four years old. It might be fine, but you should probably check to make sure this isn't incredibly stupid by today's standards.

At work we run a program whose marketing materials are heavily hexagon-based. I really like the overall look of the marketing materials, but when it came time to translate that into a web design and site, things got difficult.

Hexagons are a pain in the ass. Everything on the web is based around squares and rectangles. Things naturally want to align at the top and bottom, left and right. But not hexagons. They eschew your silly CSS grids.

The first hexagons I had to make were just part of an image. One of our marketing designers made a nice Illustrator file full of hexagons that looked great in print, but tiny alignment issues really stood out when I extracted out some resources and attempted to make it into an SVG. I reworked the whole thing, painstakingly, in Sketch, and even though I put it into production, I’m still not thrilled about how it turned out.

I recently had take the hexagons to a new level with a hexagon-based menu. I found several sites devoted to making CSS Hexagons, including an entire blog and a grid framework, but they didn’t do quite do what I needed, which was to easily arrange a known number of hexagons to match a design. This particular design calls for images inside each hexagon, no borders, and a gap between the shapes.

Achieving the hexagon shape itself is actually the easiest part: Just use a CSS clip-path! I don’t know why everybody seems to go through so much trouble of using a bunch of extra divs or pseudo-elements when CSS provides a tool that was basically built for the job. It’s not right for every job, however. We’d be out of luck if we needed a border, for instance. But clip-path works perfectly here.

After that, we’re on to arranging them. Placing one hexagon in relation to another requires knowing a bit of geometry. The ratio of the diameter of a regular hexagon to its longest vertex is 2/sqrt(3), or 1.155. The ratio of the sides to the height is sqrt(3)/2, or about .866. Once you know these things, things start to get easier. In case it’s not obvious, I’m no mathematician, so cut me some slack if my geometry terms aren’t quite right here. I’m doing my best.

So knowing these values, and doing a little bit of math on them, we can create a mixin and use that to place the hexagons on a grid.

@mixin place($row, $col, $dir: 'ew') {
  @if $dir == 'ns' {
    top: calc(var(--hexwidth) * var(--hexside) * #{$row });
    left: calc(50% + var(--hexwidth) * #{($col * .5) - .5});
  }
  @elseif $dir == 'ew' {
    top: calc(var(--hexwidth) * #{$row * .5 } * var(--hexvertex));
    left: calc(50% + var(--hexwidth) * #{($col * .75 ) - .5});
  }
}

You can plug in coordinates with this mixin, and easily position things on a grid, with values like @include place(1,1). It optionally takes an argument for the direction, either ew for east-west or ns for north-south. Everything can be rearranged at different breakpoints, so you can stack things more vertically at mobile sizes, and even skip spaces in the grid. Click through to the pen on CodePen and resize it to see things rearrange. It’s fun to watch.

You can probably tell from the top and left values that this is all using absolute positioning. That means that this whole thing ends up outside the document flow, so we need to add some height back in. There’s a mixin for that too.

@mixin gridheight($num) {
  height: calc(var(--hexwidth) * var(--hexvertex) * #{$num});
}

This mixin can take either a whole number or a number and a half, depending on how many rows you want in your grid, so you might end up with @include gridheight(3.5), or something like that.

The last thing to worry about is the starting position of this grid within its container. This is a little tricker to write a mixin for, since this is where things get a little more custom, depending on what design you want to match. For the east-west grid, if you have an odd number, set it to left: 50%; If you have an even number and you want to center it, you’ll need to offset it by half the width of a hexagon, left: calc(50% - (var(--hexwidth) / 2));. There are plenty more possibilities, and rather than try to figure them all out for use cases I don’t have, I’m leaving this one to be handled manually for now.

The whole thing is pretty easy to experiment with, and there are copious comments in the code that explain the variables, and how their values change at breakpoints. Anyway, enjoy.