Pure CSS Text effects #text #loop #layers

Isometric block text in pure CSS

One word stacked twenty times along the Z axis. Laid on a tilted floor, the copies merge into a solid extrusion under a gradient front.

  • 46 lines of CSS
  • 25 lines of HTML
  • No JavaScript
  • No dependencies
  • MIT licensed

How it works

  1. The same word is stacked 21 times, each copy one step further down the Z axis via translateZ(calc(var(--i) * -1.2px)). Seen at an angle the copies merge into a solid extrusion.
  2. rotateX(54deg) rotateZ(-32deg) lays the whole stack on an isometric-looking floor: tip it back, then turn it on the floor.
  3. Each copy gets slightly darker as --i grows (hsl(... calc(46% - var(--i) * 1.3%))) — a cheap ambient shadow down the "sides" with no lighting math.
  4. The front copy carries a gradient clipped to the text with background-clip: text; the last, deepest copy is blurred into a floor shadow instead.
  5. alternate + ease-in-out makes the sway loop seamless: the way back is the mirror image of the way there.

Key techniques

  • translateZ(calc(var(--i) * -1.2px))
  • rotateX + rotateZ floor
  • lightness from --i
  • background-clip: text

Copy-paste code

HTML 25 lines

<div class="scene">
  <div class="depth">
    <span style="--i:0">Depth</span>
    <span aria-hidden="true" style="--i:1">Depth</span>
    <span aria-hidden="true" style="--i:2">Depth</span>
    <span aria-hidden="true" style="--i:3">Depth</span>
    <span aria-hidden="true" style="--i:4">Depth</span>
    <span aria-hidden="true" style="--i:5">Depth</span>
    <span aria-hidden="true" style="--i:6">Depth</span>
    <span aria-hidden="true" style="--i:7">Depth</span>
    <span aria-hidden="true" style="--i:8">Depth</span>
    <span aria-hidden="true" style="--i:9">Depth</span>
    <span aria-hidden="true" style="--i:10">Depth</span>
    <span aria-hidden="true" style="--i:11">Depth</span>
    <span aria-hidden="true" style="--i:12">Depth</span>
    <span aria-hidden="true" style="--i:13">Depth</span>
    <span aria-hidden="true" style="--i:14">Depth</span>
    <span aria-hidden="true" style="--i:15">Depth</span>
    <span aria-hidden="true" style="--i:16">Depth</span>
    <span aria-hidden="true" style="--i:17">Depth</span>
    <span aria-hidden="true" style="--i:18">Depth</span>
    <span aria-hidden="true" style="--i:19">Depth</span>
    <span aria-hidden="true" style="--i:20">Depth</span>
  </div>
</div>

CSS 46 lines

.scene {
  perspective: 800px;
}

.depth {
  position: relative;
  transform-style: preserve-3d;
  transform: rotateX(54deg) rotateZ(-32deg);
  animation: sway 7s ease-in-out infinite alternate;
  font-size: 50px;
  font-weight: 900;
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  user-select: none;
}

.depth span {
  position: absolute;
  inset: 0;
  /* 1.2px per step: at this tilt that is about one screen pixel, so no gaps show */
  transform: translateZ(calc(var(--i) * -1.2px));
  color: hsl(252 62% calc(46% - var(--i) * 1.3%));
}

/* the front copy gives the element its size and carries the gradient */
.depth span:first-child {
  position: relative;
  display: block;
  background: linear-gradient(100deg, #2ee6d6, #8b6cff 45%, #ff4d9d);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* the last copy is the soft shadow on the floor: static, so the blur costs nothing per frame */
.depth span:last-child {
  color: rgb(0 0 0 / 0.55);
  text-shadow: 0 0 10px rgb(0 0 0 / 0.6);
  transform: translateZ(-25px) translate(-5px, 7px);
}

@keyframes sway {
  from { transform: rotateX(56deg) rotateZ(-40deg); }
  to   { transform: rotateX(48deg) rotateZ(-22deg); }
}

Sass source 52 lines

// Block text: the same word 20 times, each copy one step further down the Z axis. Seen at an
// angle the copies merge into a solid extrusion. --i is the copy's index (0 = the lit front).
.d-shadowtext {
  position: relative;
  transform-style: preserve-3d;
  // Lay the word on an isometric-looking floor: tip it back, then turn it on the floor.
  transform: rotateX(54deg) rotateZ(-32deg);
  animation: d-shadowtext-sway 7s ease-in-out infinite alternate;
  font-size: 50px;
  font-weight: 900;
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  user-select: none;

  span {
    position: absolute;
    inset: 0;
    // 1.2px per step: at this tilt that is about one screen pixel, so no gaps show.
    transform: translateZ(calc(var(--i) * -1.2px));
    // deeper copies get darker: a cheap ambient shadow down the sides
    color: hsl(252 62% calc(46% - var(--i) * 1.3%));
  }

  // The front copy gives the element its size and carries the gradient.
  span:first-child {
    position: relative;
    display: block;
    background: linear-gradient(100deg, var(--accent-2), var(--accent) 45%, var(--hot));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }

  // The last copy is the soft shadow on the floor. Static, so the blur costs nothing per frame.
  span:last-child {
    color: rgb(0 0 0 / 0.55);
    text-shadow: 0 0 10px rgb(0 0 0 / 0.6);
    transform: translateZ(-25px) translate(-5px, 7px);
  }
}

// alternate + ease-in-out = the way back is the mirror image, so the loop has no seam
@keyframes d-shadowtext-sway {
  from {
    transform: rotateX(56deg) rotateZ(-40deg);
  }

  to {
    transform: rotateX(48deg) rotateZ(-22deg);
  }
}
Standalone snippet · plain CSS, no build step, no dependencies.