Pure CSS Shapes & solids #loop #shape

Hexagonal prism in pure CSS

Six side panels in a closed ring plus two hexagon caps cut with clip-path. The apothem, (side / 2) / tan(30°), is how far each panel steps out.

  • 55 lines of CSS
  • 12 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. The six side panels are a carousel with no gaps: each gets rotateY(i × 60deg), then translateZ by the apothem, the distance from the centre to the middle of a side.
  2. Every length is a multiple of one base unit, --u, so the prism is the same share of a gallery card, the editor and a recording canvas. The numbers below are those units.
  3. Apothem = (side / 2) / tan(180° / 6). For 60-wide panels that is 51.96. Any less and the panels cross; any more and the corners open.
  4. A cap is a 2 × side by 2 × apothem box cut to a hexagon with clip-path. clip-path would flatten a 3D container, but the caps have no 3D children, so it is safe here.
  5. rotateX(90deg) lays a cap flat; translateZ(height / 2) then lifts it along its new normal to the top (and -90deg for the bottom).

Key techniques

  • rotateY(i × 60°) + translateZ(apothem)
  • clip-path: polygon() caps
  • rotateX(90deg) to lay a cap flat
  • two-axis tumble

Copy-paste code

HTML 12 lines

<div class="scene">
  <div class="prism">
    <i style="--i:0"></i>
    <i style="--i:1"></i>
    <i style="--i:2"></i>
    <i style="--i:3"></i>
    <i style="--i:4"></i>
    <i style="--i:5"></i>
    <b></b>
    <b></b>
  </div>
</div>

CSS 55 lines

.scene {
  /* one base unit: every length below is a multiple of it, so the prism is the same share of a
     card, the editor, a full screen and a recording canvas */
  --u: 0.33vmin;
  perspective: calc(800 * var(--u));
}

.prism {
  --w: calc(60 * var(--u));    /* one side of the hexagon = one panel */
  --h: calc(110 * var(--u));
  --r: calc(51.96 * var(--u)); /* apothem = (w / 2) / tan(180deg / 6) */
  position: relative;
  width: var(--w);
  height: var(--h);
  transform-style: preserve-3d;
  animation: tumble 18s linear infinite;
}

.prism i {
  position: absolute;
  inset: 0;
  background: rgb(139 108 255 / 0.24);
  border: calc(1 * var(--u)) solid rgb(139 108 255 / 0.75);
  box-shadow: inset 0 0 calc(24 * var(--u)) rgb(139 108 255 / 0.3);
  /* turn to face outward, THEN step out along that direction */
  transform: rotateY(calc(var(--i) * 60deg)) translateZ(var(--r));
}

.prism i:nth-child(even) {
  background: rgb(46 230 214 / 0.2);
  border-color: rgb(46 230 214 / 0.75);
  box-shadow: inset 0 0 calc(24 * var(--u)) rgb(46 230 214 / 0.3);
}

/* caps: a regular hexagon's corner radius equals its side, so the box is 2w by 2r */
.prism b {
  position: absolute;
  left: calc(50% - var(--w));
  top: calc(50% - var(--r));
  width: calc(var(--w) * 2);
  height: calc(var(--r) * 2);
  clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
  background: radial-gradient(circle, rgb(255 77 157 / 0.12) 20%, rgb(255 77 157 / 0.55));
  transform: rotateX(90deg) translateZ(calc(var(--h) / 2));
}

.prism b + b {
  transform: rotateX(-90deg) translateZ(calc(var(--h) / 2));
}

/* -25deg + 360deg = 335deg: the end pose equals the start pose, so the loop is seamless */
@keyframes tumble {
  from { transform: rotateX(-25deg) rotateY(0deg); }
  to   { transform: rotateX(335deg) rotateY(360deg); }
}
Standalone snippet · plain CSS, no build step, no dependencies.