Pure CSS Shapes & solids #loop #shape

Octahedron in pure CSS

Eight triangles hinged on a square equator, leaning in by 35.26° so they meet at two apexes. One rule builds all of them: scaleY(-1) flips the bottom four.

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

How it works

  1. An octahedron is two square pyramids glued at their base. Every face is the same equilateral triangle (clip-path), standing on one edge of the square "equator".
  2. Every length is a multiple of one base unit, --u, so the solid is the same share of a gallery card, the editor and a recording canvas.
  3. Hinge each triangle on its bottom edge (transform-origin: 50% 100%) and lean it in by 90° − atan(√2) ≈ 35.26°: exactly enough for four tips to meet on the axis.
  4. The bottom pyramid reuses the same rule. --s: -1 flips the triangle to point down with scaleY(-1) and reverses the lean, so one line of CSS builds all eight faces.
  5. clip-path also clips borders away, so the slanted edges are painted: a to top left gradient's 50% line runs exactly along its box diagonal.

Key techniques

  • clip-path triangles
  • lean = 90° − atan(√2)
  • --s: 1 / −1 flips half the faces
  • edges drawn with "to corner" gradients

Copy-paste code

HTML 12 lines

<div class="scene">
  <div class="octa">
    <i style="--i:0; --s:1"></i>
    <i style="--i:1; --s:1"></i>
    <i style="--i:2; --s:1"></i>
    <i style="--i:3; --s:1"></i>
    <i style="--i:0; --s:-1"></i>
    <i style="--i:1; --s:-1"></i>
    <i style="--i:2; --s:-1"></i>
    <i style="--i:3; --s:-1"></i>
  </div>
</div>

CSS 53 lines

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

.octa {
  --a: calc(110 * var(--u)); /* edge length */
  position: relative;
  width: var(--a);
  height: var(--a);
  transform-style: preserve-3d;
  animation: spin 12s linear infinite;
}

.octa i {
  --c: 139 108 255;
  --alpha: 0.22;
  --edge: rgb(var(--c) / 0.85);
  position: absolute;
  left: 0;
  bottom: 50%;             /* bottom edge on the equator, through the centre */
  width: var(--a);
  height: calc(95.26 * var(--u)); /* triangle height = a × √3 / 2 */
  transform-origin: 50% 100%;
  transform:
    rotateY(calc(var(--i) * 90deg))
    translateZ(calc(var(--a) / 2))
    rotateX(calc(var(--s) * 35.26deg))  /* lean in: 90deg - atan(√2) */
    scaleY(var(--s));                   /* -1 flips the bottom four downward */
  clip-path: polygon(50% 0, 0 100%, 100% 100%);
  background:
    linear-gradient(to top left, transparent calc(50% - 1.5 * var(--u)), var(--edge) calc(50% - 1.5 * var(--u)) 50%, transparent 50%) left / 50% 100% no-repeat,
    linear-gradient(to top right, transparent calc(50% - 1.5 * var(--u)), var(--edge) calc(50% - 1.5 * var(--u)) 50%, transparent 50%) right / 50% 100% no-repeat,
    linear-gradient(var(--edge), var(--edge)) bottom / 100% calc(1.5 * var(--u)) no-repeat,
    linear-gradient(to top, rgb(var(--c) / var(--alpha)), rgb(var(--c) / 0.08));
}

.octa i:nth-child(n + 5) {
  --c: 46 230 214;
}

/* checkerboard the brightness so neighbouring faces always differ */
.octa i:nth-child(-n + 4):nth-child(odd),
.octa i:nth-child(n + 5):nth-child(even) {
  --alpha: 0.42;
}

@keyframes spin {
  from { transform: rotateZ(14deg) rotateX(-16deg) rotateY(0deg); }
  to   { transform: rotateZ(14deg) rotateX(-16deg) rotateY(360deg); }
}
Standalone snippet · plain CSS, no build step, no dependencies.