30 CSS Grid Layouts14 / 30

Pure CSSMIT licensed

Hexagonal Honeycomb

A true interlocking honeycomb: pointy-top clip-path hexagons in flex rows that overlap the previous row by 30% of hex width, with alternate rows shifted half a hex sideways so the peaks nest into the previous row's bottom notches. Amber cells glow on hover like dripping honey. The showpiece layout for team pages, skill matrices and NFT-free hexagon needs.

Published Updated

Live Demo
Try it

The code

<section class="cgl-14" aria-label="Hexagonal honeycomb grid demo">
  <ul class="cgl-14__comb">
    <li class="cgl-14__row"><a href="#0"><strong>UI</strong></a><a href="#0"><strong>UX</strong></a><a href="#0"><strong>CSS</strong></a><a href="#0"><strong>JS</strong></a></li>
    <li class="cgl-14__row cgl-14__row--offset"><a href="#0"><strong>A11y</strong></a><a href="#0"><strong>Perf</strong></a><a href="#0"><strong>SVG</strong></a></li>
    <li class="cgl-14__row"><a href="#0"><strong>API</strong></a><a href="#0"><strong>Git</strong></a><a href="#0"><strong>SEO</strong></a></li>
  </ul>
</section>
.cgl-14,
.cgl-14 *,
.cgl-14 *::before,
.cgl-14 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cgl-14 {
  width: 100%;
  min-height: 100vh;
  --hex: 104px;
  --honey: oklch(0.8 0.15 80);
  --bg: oklch(0.18 0.03 60);
  font-family: system-ui,sans-serif;
  background: radial-gradient(100% 120% at 50% 0%,oklch(0.24 0.05 70/.9),transparent 60%),var(--bg);
  padding: 2rem 1.4rem;
  display: grid;
  place-items: center;
}
/* Honeycomb via explicit rows: each <li class="cgl-14__row"> is a flex
   row of hexagons; alternate rows carry --row--offset which shifts
   them half a hex sideways AND pulls them up 25% of hex height so the
   hexes interlock instead of stacking with gaps. This is the classic
   flex-based honeycomb pattern (Coyier / MDN) — more predictable than
   grid auto-flow arithmetic when the tile count doesn't divide evenly
   into row counts. */
/* IMPORTANT: align-items:flex-start (not center). With align-items:
   center each row would independently center itself along its own
   width — but row 2 has 3 hexes and row 1/3 have 4/3 hexes, so their
   natural centers differ. Setting all rows to share the LEFT edge
   makes the --offset shift do the actual half-hex work; otherwise
   the auto-centering fights the offset and the middle row drifts
   right (the exact bug from the previous fix pass). */

.cgl-14 .cgl-14__comb {
  list-style: none;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
/* gap:0 within a row — pointy-top hexes touch at flat vertical edges,
   and any gap accumulates differently in rows of different tile counts
   (row 1 has 3 gaps × 2px = +6px; row 2 has 2 gaps × 2px = +4px),
   which drifts the notch positions and prevents the offset row's peaks
   from landing precisely in the row-above notches. Zero gap makes
   hex/2 the mathematically exact half-hex offset. */

.cgl-14 .cgl-14__row {
  list-style: none;
  display: flex;
  gap: 0;
}
/* POINTY-TOP honeycomb math (much simpler than flat-top for row-based
   layouts): each hex box is W wide × 1.155W tall (H = W × 2/√3, since
   the vertical span is corner-to-corner). Same-row hexes touch flat
   vertical edges → no overlap needed within a row (gap:2px is just
   breathing room). Consecutive rows overlap by 25% of hex height (0.25H
   = 0.2887W), so margin-block-start = -0.2887W pulls each new row into
   the previous row's bottom-notch region. Offset rows shift W/2
   sideways so their peaks slot into the diamond gaps between the
   previous row's hex-tops. */

.cgl-14 .cgl-14__row + .cgl-14__row {
  margin-block-start: calc(var(--hex) * -0.3);
}

.cgl-14 .cgl-14__row--offset {
  margin-inline-start: calc(var(--hex) / 2);
}
/* Exact half-hex shift; peaks land in row-above notches. */
/* Adjacent hexes with gap:0 need visual separation to read as
   individual cells rather than one merged blob. Use a nested inner
   hex trick: the outer <a> is a slightly larger hex acting as a
   subtle border; the inner ::before is the fill hex inset by 2px on
   each side. The 2px gap between the outer edge and inner fill reads
   as a hairline outline WITHOUT breaking the honeycomb math (the
   outer hex tiles remain edge-to-edge). */

.cgl-14 .cgl-14__comb a {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: var(--hex);
  aspect-ratio: 1/1.1547;
  clip-path: polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background: color-mix(in oklab,var(--honey) 40%,var(--bg));
  text-decoration: none;
  transition: background .25s ease,scale .25s ease;
}

.cgl-14 .cgl-14__comb a::before {
  content: '';
  position: absolute;
  inset: 3px;
  clip-path: polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background: color-mix(in oklab,var(--honey) 22%,var(--bg));
  pointer-events: none;
  transition: background .25s ease;
}

.cgl-14 .cgl-14__comb strong {
  position: relative;
  z-index: 1;
}

.cgl-14 .cgl-14__comb a:hover,
.cgl-14 .cgl-14__comb a:focus-visible {
  scale: 1.06;
  outline: none;
  z-index: 1;
}

.cgl-14 .cgl-14__comb a:hover::before,
.cgl-14 .cgl-14__comb a:focus-visible::before {
  background: var(--honey);
}

.cgl-14 .cgl-14__comb a:hover strong,
.cgl-14 .cgl-14__comb a:focus-visible strong {
  color: var(--bg);
}

.cgl-14 .cgl-14__comb strong {
  font-size: .82rem;
  letter-spacing: .06em;
  color: var(--honey);
}

@media (prefers-reduced-motion: reduce) {
  .cgl-14 .cgl-14__comb a {
    transition: none;
  }
}
/* No JavaScript — pointy-top hexes in flex rows with -30% margin-block-start overlap; alternate rows shift half a hex sideways via margin-inline-start so peaks nest into the row-above notches; clip-path cuts each hexagon. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Grid Layout from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Hexagonal Honeycomb
Source: https://codefronts.com/layouts/css-grid-layouts/hexagonal-honeycomb/

A true interlocking honeycomb: pointy-top clip-path hexagons in flex rows that overlap the previous row by 30% of hex width, with alternate rows shifted half a hex sideways so the peaks nest into the previous row's bottom notches. Amber cells glow on hover like dripping honey. The showpiece layout for team pages, skill matrices and NFT-free hexagon needs.
## HTML
```html
<section class="cgl-14" aria-label="Hexagonal honeycomb grid demo">
  <ul class="cgl-14__comb">
    <li class="cgl-14__row"><a href="#0"><strong>UI</strong></a><a href="#0"><strong>UX</strong></a><a href="#0"><strong>CSS</strong></a><a href="#0"><strong>JS</strong></a></li>
    <li class="cgl-14__row cgl-14__row--offset"><a href="#0"><strong>A11y</strong></a><a href="#0"><strong>Perf</strong></a><a href="#0"><strong>SVG</strong></a></li>
    <li class="cgl-14__row"><a href="#0"><strong>API</strong></a><a href="#0"><strong>Git</strong></a><a href="#0"><strong>SEO</strong></a></li>
  </ul>
</section>
```
## CSS
```css
.cgl-14,
.cgl-14 *,
.cgl-14 *::before,
.cgl-14 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cgl-14 {
  width: 100%;
  min-height: 100vh;
  --hex: 104px;
  --honey: oklch(0.8 0.15 80);
  --bg: oklch(0.18 0.03 60);
  font-family: system-ui,sans-serif;
  background: radial-gradient(100% 120% at 50% 0%,oklch(0.24 0.05 70/.9),transparent 60%),var(--bg);
  padding: 2rem 1.4rem;
  display: grid;
  place-items: center;
}
/* Honeycomb via explicit rows: each <li class="cgl-14__row"> is a flex
   row of hexagons; alternate rows carry --row--offset which shifts
   them half a hex sideways AND pulls them up 25% of hex height so the
   hexes interlock instead of stacking with gaps. This is the classic
   flex-based honeycomb pattern (Coyier / MDN) — more predictable than
   grid auto-flow arithmetic when the tile count doesn't divide evenly
   into row counts. */
/* IMPORTANT: align-items:flex-start (not center). With align-items:
   center each row would independently center itself along its own
   width — but row 2 has 3 hexes and row 1/3 have 4/3 hexes, so their
   natural centers differ. Setting all rows to share the LEFT edge
   makes the --offset shift do the actual half-hex work; otherwise
   the auto-centering fights the offset and the middle row drifts
   right (the exact bug from the previous fix pass). */

.cgl-14 .cgl-14__comb {
  list-style: none;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
/* gap:0 within a row — pointy-top hexes touch at flat vertical edges,
   and any gap accumulates differently in rows of different tile counts
   (row 1 has 3 gaps × 2px = +6px; row 2 has 2 gaps × 2px = +4px),
   which drifts the notch positions and prevents the offset row's peaks
   from landing precisely in the row-above notches. Zero gap makes
   hex/2 the mathematically exact half-hex offset. */

.cgl-14 .cgl-14__row {
  list-style: none;
  display: flex;
  gap: 0;
}
/* POINTY-TOP honeycomb math (much simpler than flat-top for row-based
   layouts): each hex box is W wide × 1.155W tall (H = W × 2/√3, since
   the vertical span is corner-to-corner). Same-row hexes touch flat
   vertical edges → no overlap needed within a row (gap:2px is just
   breathing room). Consecutive rows overlap by 25% of hex height (0.25H
   = 0.2887W), so margin-block-start = -0.2887W pulls each new row into
   the previous row's bottom-notch region. Offset rows shift W/2
   sideways so their peaks slot into the diamond gaps between the
   previous row's hex-tops. */

.cgl-14 .cgl-14__row + .cgl-14__row {
  margin-block-start: calc(var(--hex) * -0.3);
}

.cgl-14 .cgl-14__row--offset {
  margin-inline-start: calc(var(--hex) / 2);
}
/* Exact half-hex shift; peaks land in row-above notches. */
/* Adjacent hexes with gap:0 need visual separation to read as
   individual cells rather than one merged blob. Use a nested inner
   hex trick: the outer <a> is a slightly larger hex acting as a
   subtle border; the inner ::before is the fill hex inset by 2px on
   each side. The 2px gap between the outer edge and inner fill reads
   as a hairline outline WITHOUT breaking the honeycomb math (the
   outer hex tiles remain edge-to-edge). */

.cgl-14 .cgl-14__comb a {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: var(--hex);
  aspect-ratio: 1/1.1547;
  clip-path: polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background: color-mix(in oklab,var(--honey) 40%,var(--bg));
  text-decoration: none;
  transition: background .25s ease,scale .25s ease;
}

.cgl-14 .cgl-14__comb a::before {
  content: '';
  position: absolute;
  inset: 3px;
  clip-path: polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background: color-mix(in oklab,var(--honey) 22%,var(--bg));
  pointer-events: none;
  transition: background .25s ease;
}

.cgl-14 .cgl-14__comb strong {
  position: relative;
  z-index: 1;
}

.cgl-14 .cgl-14__comb a:hover,
.cgl-14 .cgl-14__comb a:focus-visible {
  scale: 1.06;
  outline: none;
  z-index: 1;
}

.cgl-14 .cgl-14__comb a:hover::before,
.cgl-14 .cgl-14__comb a:focus-visible::before {
  background: var(--honey);
}

.cgl-14 .cgl-14__comb a:hover strong,
.cgl-14 .cgl-14__comb a:focus-visible strong {
  color: var(--bg);
}

.cgl-14 .cgl-14__comb strong {
  font-size: .82rem;
  letter-spacing: .06em;
  color: var(--honey);
}

@media (prefers-reduced-motion: reduce) {
  .cgl-14 .cgl-14__comb a {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — pointy-top hexes in flex rows with -30% margin-block-start overlap; alternate rows shift half a hex sideways via margin-inline-start so peaks nest into the row-above notches; clip-path cuts each hexagon. */
```

How this works

Pointy-top hexagons are the natural fit for row-based honeycomb layouts: each hex has a flat vertical edge on left and right (so adjacent same-row hexes touch cleanly with no overlap needed) and a peak on top/bottom (so the peaks tuck into the notches between adjacent hexes in the row above/below). This demo uses the classic flex-row honeycomb pattern: each visual row is a <li class="cgl-14__row"> that flexes its hex tiles inline; subsequent rows use margin-block-start: calc(var(--hex) * -0.3) to pull upward so their peaks slot into the previous row's bottom notches, and offset rows carry margin-inline-start: calc(var(--hex) / 2) to shift half a hex sideways.

Every hex tile has aspect-ratio: 1 / 1.1547 (the true pointy-top hex ratio, 1 : 2/√3) and a 6-point clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%). Flex-row grouping is more predictable than grid auto-flow arithmetic when the tile count doesn't divide evenly by row width (this comb has a 4-3-3 pattern for 10 items — impossible to express cleanly with a single grid template). Each row is a separate flex container, so tile counts per row can vary without breaking the interlock. The --hex custom property drives ALL the geometry — resize the whole comb by changing one value.

Make it yours

  • Resize the comb with the single --hex custom property — all arithmetic derives from it.
  • Feed real content: each cell is an <a>, ready for avatars (add an img with the same clip-path).
  • Rotate the palette from honey to any hue by changing --honey.
  • Switch to flat-top hexes by rotating the polygon 30° and swapping aspect-ratio to 1/0.866 — but you'll need to redo the row overlap math since flat-top honeycombs are more complex.
  • Change row counts by adding a <li class="cgl-14__row"> (default) or <li class="cgl-14__row cgl-14__row--offset"> (shifted half-hex).

Gotchas — read before shipping

  • clip-path clips the focus outline too — the focus style must be an inset effect (background/scale change, included), not an outline.
  • The interlock math assumes uniform tile size; mixed sizes break instantly.
  • Each visual row needs a wrapping <li> — the flex overlap only works between siblings of the outer <ul>.
  • Pointy-top hexes have height > width (1.1547×) — the aspect-ratio must match or the peaks look stretched.

Browser support

ChromeSafariFirefoxEdge
111+15.4+110+111+

cos() in aspect-ratio is the newest piece (swap for 0.866 to reach 2021 browsers); clip-path polygons are universal since 2020.

Techniques used in this demo

Search CodeFronts

Loading…