Pure CSS Text effects #text #faux-3d #loop

Extruded text in pure CSS

A stack of hard text-shadows, each one step further and a little darker, reads as solid depth.

  • 37 lines of CSS
  • 3 lines of HTML
  • No JavaScript
  • No dependencies
  • Snippet under MIT
GitHub

How it works

  1. There is no real depth here — it is many hard-edged text-shadows, each offset one unit further.
  2. Darken each layer slightly and the stack reads as a solid side wall.
  3. Finish with one blurred shadow for the drop shadow on the "ground".
  4. The list is written out in full: one line per layer, so every step can be read and changed.
  5. Every length is a multiple of one base unit, --u, the offsets included, so the whole extrusion keeps its proportions on a gallery card, in the editor and in a recording canvas.

Key techniques

  • text-shadow stack
  • darker layer per step
  • rotateY rocking

Copy-paste code

HTML 3 lines

<div class="scene">
  <h1 class="extruded">3D<br>DEPTH</h1>
</div>

CSS 37 lines

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

/* Two lines, because one is a shape the band cannot hold: DEPTH on its own is two and a half
   times wider than it is tall, so sized to the 92vmin width limit it stands under the 40vmin
   floor. A kicker line over the word is what a headline does anyway. */
.extruded {
  margin: 0;
  font: 900 calc(80 * var(--u))/1.04 Inter, system-ui;
  letter-spacing: 0.04em;
  text-align: center;
  color: #fff;
  text-shadow:
    calc(1 * var(--u)) calc(1 * var(--u)) 0 #7f63e8,
    calc(2 * var(--u)) calc(2 * var(--u)) 0 #785ddb,
    calc(3 * var(--u)) calc(3 * var(--u)) 0 #7057cd,
    calc(4 * var(--u)) calc(4 * var(--u)) 0 #6951c0,
    calc(5 * var(--u)) calc(5 * var(--u)) 0 #614bb2,
    calc(6 * var(--u)) calc(6 * var(--u)) 0 #5a45a5,
    calc(7 * var(--u)) calc(7 * var(--u)) 0 #523f97,
    calc(8 * var(--u)) calc(8 * var(--u)) 0 #4b398a,
    calc(14 * var(--u)) calc(18 * var(--u)) calc(18 * var(--u)) rgb(0 0 0 / 0.5);
  animation: rock 5s ease-in-out infinite alternate;
}

/* The lift is 12 units, not 20. At 20 the headline sat 5.8vmin above the middle of its canvas,
   past the 4vmin the view contract allows -- it read as centred only while the models asked for
   system-ui, whose shorter ink box hid part of the offset. Inter's does not. Some lift is still
   wanted: the drop shadow falls down and to the right, so a headline hung dead centre looks low. */
@keyframes rock {
  from { transform: translateY(calc(-12 * var(--u))) rotateY(-32deg) rotateX(12deg); }
  to   { transform: translateY(calc(-12 * var(--u))) rotateY(32deg)  rotateX(-6deg); }
}
Standalone snippet · plain CSS, no build step, no dependencies.