22 CSS Image Galleries01 / 22

Pure CSSMIT licensed

CSS Hexagon Honeycomb Gallery

An interlocking honeycomb where square photos are clipped into hexagons and packed into offset rows with no gaps — a dense, geometric gallery for teams, portfolios and product ranges. Built entirely with clip-path.

Published

Live Demo
Try it

The code

<section class="gl-04" aria-label="Hexagon honeycomb gallery">
  <div class="gl-04__comb">
    <div class="gl-04__row">
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/portrait?lock=401" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Ava</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/portrait,man?lock=402" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Liam</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/portrait,woman?lock=403" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Noa</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/face?lock=404" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Kai</span></a>
    </div>
    <div class="gl-04__row gl-04__row--offset">
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/model?lock=405" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Mia</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/headshot?lock=406" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Eli</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/person?lock=407" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Zoe</span></a>
    </div>
    <div class="gl-04__row">
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/smile?lock=408" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Ivo</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/profile?lock=409" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Ana</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/portrait,studio?lock=410" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Ben</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/portrait,dark?lock=411" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Uma</span></a>
    </div>
  </div>
</section>
.gl-04,
.gl-04 *,
.gl-04 *::before,
.gl-04 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.gl-04 ::selection {
  background: oklch(0.65 0.18 45);
  color: #fff;
}

.gl-04 {
  --hex: 132px;
  --accent: oklch(0.62 0.19 45);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  padding: 40px 20px;
  background: #161318;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gl-04__comb {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.gl-04__row {
  display: flex;
  gap: 8px;
}

.gl-04__row+.gl-04__row {
  margin-top: calc(var(--hex) * -0.28);
}
/* Middle row auto-centres via align-items:center on .gl-04__comb.
   The row-width difference (4-hex row is one hex + one gap wider than
   3-hex row) creates the honeycomb offset for free — no margin-left
   push needed. Adding one caused a visible right-shift. */

.gl-04__hex {
  position: relative;
  width: var(--hex);
  aspect-ratio: 1/1.1547;
  clip-path: polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background: #241f28;
  overflow: hidden;
  text-decoration: none;
  display: block;
}

.gl-04__hex img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1);
  transition: transform .45s;
}

.gl-04__hex::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg,color-mix(in oklch,var(--accent) 70%,transparent),transparent 60%);
  opacity: 0;
  transition: opacity .35s;
}

.gl-04__lbl {
  position: absolute;
  inset: auto 0 22% 0;
  text-align: center;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .04em;
  z-index: 2;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .35s,transform .35s;
  text-shadow: 0 1px 4px rgba(0,0,0,.6);
}

.gl-04__hex:hover img,
.gl-04__hex:focus-visible img {
  transform: scale(1.12);
}

.gl-04__hex:hover::after,
.gl-04__hex:focus-visible::after {
  opacity: 1;
}

.gl-04__hex:hover .gl-04__lbl,
.gl-04__hex:focus-visible .gl-04__lbl {
  opacity: 1;
  transform: translateY(0);
}

.gl-04__hex:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

@media (max-width: 520px) {
  .gl-04 {
    --hex: 96px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gl-04__hex img,
    .gl-04__hex::after,
    .gl-04__lbl {
    transition: none;
  }
}
/* No JavaScript — clip-path hexagons packed into offset rows with negative margins. */
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 Image Gallerie 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: CSS Hexagon Honeycomb Gallery
Source: https://codefronts.com/snippets/css-image-gallery/css-hexagon-honeycomb-gallery/

An interlocking honeycomb where square photos are clipped into hexagons and packed into offset rows with no gaps — a dense, geometric gallery for teams, portfolios and product ranges. Built entirely with clip-path.
## HTML
```html
<section class="gl-04" aria-label="Hexagon honeycomb gallery">
  <div class="gl-04__comb">
    <div class="gl-04__row">
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/portrait?lock=401" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Ava</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/portrait,man?lock=402" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Liam</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/portrait,woman?lock=403" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Noa</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/face?lock=404" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Kai</span></a>
    </div>
    <div class="gl-04__row gl-04__row--offset">
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/model?lock=405" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Mia</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/headshot?lock=406" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Eli</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/person?lock=407" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Zoe</span></a>
    </div>
    <div class="gl-04__row">
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/smile?lock=408" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Ivo</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/profile?lock=409" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Ana</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/portrait,studio?lock=410" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Ben</span></a>
      <a class="gl-04__hex" href="#"><img src="https://loremflickr.com/300/300/portrait,dark?lock=411" alt="Team member portrait" loading="lazy" decoding="async"><span class="gl-04__lbl">Uma</span></a>
    </div>
  </div>
</section>
```
## CSS
```css
.gl-04,
.gl-04 *,
.gl-04 *::before,
.gl-04 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.gl-04 ::selection {
  background: oklch(0.65 0.18 45);
  color: #fff;
}

.gl-04 {
  --hex: 132px;
  --accent: oklch(0.62 0.19 45);
  font-family: 'Segoe UI',system-ui,sans-serif;
  width: 100%;
  min-height: 100vh;
  padding: 40px 20px;
  background: #161318;
  display: flex;
  align-items: center;
  justify-content: center;
}

.gl-04__comb {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.gl-04__row {
  display: flex;
  gap: 8px;
}

.gl-04__row+.gl-04__row {
  margin-top: calc(var(--hex) * -0.28);
}
/* Middle row auto-centres via align-items:center on .gl-04__comb.
   The row-width difference (4-hex row is one hex + one gap wider than
   3-hex row) creates the honeycomb offset for free — no margin-left
   push needed. Adding one caused a visible right-shift. */

.gl-04__hex {
  position: relative;
  width: var(--hex);
  aspect-ratio: 1/1.1547;
  clip-path: polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);
  background: #241f28;
  overflow: hidden;
  text-decoration: none;
  display: block;
}

.gl-04__hex img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1);
  transition: transform .45s;
}

.gl-04__hex::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg,color-mix(in oklch,var(--accent) 70%,transparent),transparent 60%);
  opacity: 0;
  transition: opacity .35s;
}

.gl-04__lbl {
  position: absolute;
  inset: auto 0 22% 0;
  text-align: center;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .04em;
  z-index: 2;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity .35s,transform .35s;
  text-shadow: 0 1px 4px rgba(0,0,0,.6);
}

.gl-04__hex:hover img,
.gl-04__hex:focus-visible img {
  transform: scale(1.12);
}

.gl-04__hex:hover::after,
.gl-04__hex:focus-visible::after {
  opacity: 1;
}

.gl-04__hex:hover .gl-04__lbl,
.gl-04__hex:focus-visible .gl-04__lbl {
  opacity: 1;
  transform: translateY(0);
}

.gl-04__hex:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

@media (max-width: 520px) {
  .gl-04 {
    --hex: 96px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gl-04__hex img,
    .gl-04__hex::after,
    .gl-04__lbl {
    transition: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — clip-path hexagons packed into offset rows with negative margins. */
```

How this works

Each cell is clipped to a flat-top hexagon with clip-path: polygon(). Cells are arranged in horizontal rows; alternate rows are shifted half a hexagon sideways and pulled up so the points nest into the gaps of the row above — the geometry that makes a true honeycomb tessellate.

Every hexagon wraps a real <a> around an <img>; a coloured overlay and label fade in on hover/focus, and the clip keeps the zoom neatly inside the hex shape.

Make it yours

  • Switch to pointy-top hexagons by rotating the polygon points 90° and offsetting rows vertically instead.
  • Resize the comb by changing the hex width variable; the row offsets are derived from it.
  • Recolour the hover overlay or replace the label with an icon.
  • Add or remove cells per row — keep alternate rows one cell shorter for clean edges.

Gotchas — read before shipping

  • Hexagon packing needs precise negative margins tuned to the hex height (~0.75×) — changing the size means retuning the row offset.
  • clip-path clips paint but not the box, so overlap rows with margins, and keep focus rings visible outside the clip.
  • Provide meaningful alt text; the hex shape is decorative and shouldn't hide content from AT.

Browser support

ChromeSafariFirefoxEdge
111+16.2+113+111+

Floor set by oklch(), color-mix() as this demo is written. The core technique itself goes back further — Chrome 55+.

clip-path: polygon() — supported in all current browsers (older Safari needs the -webkit- prefix).

Techniques used in this demo

Search CodeFronts

Loading…