Pure CSS Text effects #loop #text

Layered text in pure CSS

Real depth this time: ten copies of the word stacked along Z, so perspective is correct.

  • 28 lines of CSS
  • 15 lines of HTML
  • No JavaScript
  • No dependencies
  • MIT licensed

How it works

  1. The shadow-stack text is a 2D trick: its "depth" always points the same way. This version has real depth.
  2. Ten copies of the word are stacked with position: absolute and pushed back 3px × i along Z.
  3. Each copy is darker than the one in front of it, which shades the side wall.
  4. Rotate the parent and the side wall appears on the correct side, with true perspective. Cost: more DOM, and the copies need aria-hidden.

Key techniques

  • stacked copies with translateZ
  • aria-hidden duplicates
  • compare with the shadow version

Copy-paste code

HTML 15 lines

<div class="scene">
  <h1 class="deep">
    <b>DEEP</b>
    <span style="--i:1" aria-hidden="true">DEEP</span>
    <span style="--i:2" aria-hidden="true">DEEP</span>
    <span style="--i:3" aria-hidden="true">DEEP</span>
    <span style="--i:4" aria-hidden="true">DEEP</span>
    <span style="--i:5" aria-hidden="true">DEEP</span>
    <span style="--i:6" aria-hidden="true">DEEP</span>
    <span style="--i:7" aria-hidden="true">DEEP</span>
    <span style="--i:8" aria-hidden="true">DEEP</span>
    <span style="--i:9" aria-hidden="true">DEEP</span>
    <span style="--i:10" aria-hidden="true">DEEP</span>
  </h1>
</div>

CSS 28 lines

.scene {
  perspective: 800px;
}

.deep {
  position: relative;
  margin: 0;
  font: 900 5.5rem system-ui;
  transform-style: preserve-3d;
  animation: deep-rock 5s ease-in-out infinite alternate;
}

.deep b {
  position: relative;
  color: #fff;
}

.deep span {
  position: absolute;
  inset: 0;
  color: color-mix(in srgb, #ff4d9d calc(100% - var(--i) * 7%), #000);
  transform: translateZ(calc(var(--i) * -3px));
}

@keyframes deep-rock {
  from { transform: rotateY(-38deg) rotateX(10deg); }
  to   { transform: rotateY(38deg)  rotateX(-8deg); }
}

Sass source 32 lines

.d-layertext {
  position: relative;
  font-size: 64px;
  font-weight: 900;
  letter-spacing: 0.03em;
  transform-style: preserve-3d;
  animation: d-layertext-rock 5s ease-in-out infinite alternate;

  // the readable front copy
  b {
    position: relative;
    color: #fff;
  }

  // Ten copies pushed back 3px each (--i is 1…10), getting darker with depth.
  span {
    position: absolute;
    inset: 0;
    color: color-mix(in srgb, var(--hot) calc(100% - var(--i) * 7%), #000);
    transform: translateZ(calc(var(--i) * -3px));
  }
}

@keyframes d-layertext-rock {
  from {
    transform: rotateY(-38deg) rotateX(10deg);
  }

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