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.

  • 52 lines of CSS
  • 12 lines of HTML
  • No JavaScript
  • No dependencies
  • MIT licensed
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. Apothem = (side / 2) / tan(180° / 6). For 60px panels that is 51.96px. Any less and the panels cross; any more and the corners open.
  3. 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.
  4. 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 52 lines

.scene {
  perspective: 800px;
}

.prism {
  --w: 60px;    /* one side of the hexagon = one panel */
  --h: 110px;
  --r: 51.96px; /* 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: 1px solid rgb(139 108 255 / 0.75);
  box-shadow: inset 0 0 24px 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 24px 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); }
}

Sass source 59 lines

@use 'sass:math';
@use '../mixins' as *;

$n: 6;
$w: 52px; // width of one side panel = side of the hexagon
$h: 96px;
$r: math.div($w * 0.5, math.tan(math.div(180deg, $n))); // apothem: centre to the middle of a side
$R: $w; // a regular hexagon's corner radius equals its side

.d-prism {
  position: relative;
  width: $w;
  height: $h;
  transform-style: preserve-3d;
  animation: d-prism-tumble 18s linear infinite;

  // side panels: a six-panel ring, exactly like a carousel with no gaps
  i {
    position: absolute;
    inset: 0;
    transform: rotateY(calc(var(--i) * #{math.div(360deg, $n)})) translateZ($r);
    @include face(var(--accent), 24%);

    &:nth-child(even) {
      @include face(var(--accent-2), 20%);
    }
  }

  // caps: a flat-topped hexagon cut out of a 2R x 2r box, laid flat on each end.
  // clip-path is safe here because the caps are leaves (no 3D children to flatten).
  b {
    position: absolute;
    left: calc(50% - #{$R});
    top: calc(50% - #{$r});
    width: $R * 2;
    height: $r * 2;
    clip-path: polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%);
    background: radial-gradient(
      circle,
      color-mix(in srgb, var(--hot) 12%, transparent) 20%,
      color-mix(in srgb, var(--hot) 55%, transparent) 100%
    );
    transform: rotateX(90deg) translateZ($h * 0.5);

    & + b {
      transform: rotateX(-90deg) translateZ($h * 0.5);
    }
  }
}

@keyframes d-prism-tumble {
  from {
    transform: rotateX(-25deg) rotateY(0deg);
  }

  to {
    transform: rotateX(335deg) rotateY(360deg);
  }
}
Standalone snippet · plain CSS, no build step, no dependencies.