30 CSS Text Animations08 / 30

Pure CSSMIT licensed

CSS Staggered Text Letter Reveal Keyframes

Per-letter choreography for luxury brand intros: a masked serif cascade with a gold rule that draws itself, and a center-out spring assemble powered by the modern linear() easing function.

Published Updated

Live Demo
Try it

The code

<div class="cat-08-group">
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;600;700&display=swap" rel="stylesheet">
<section class="cat-08a" aria-label="Luxury letter cascade reveal">
  <div class="cat-08a__stage">
    <h1 class="cat-08a__word" aria-label="VELOUR">
      <span class="cat-08a__mask" aria-hidden="true"><i style="--i:0">V</i><i style="--i:1">E</i><i style="--i:2">L</i><i style="--i:3">O</i><i style="--i:4">U</i><i style="--i:5">R</i></span>
    </h1>
    <p class="cat-08a__tag" aria-hidden="true"><span>MAISON DE COUTURE — PARIS</span></p>
  </div>
</section>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet">
<section class="cat-08b" aria-label="Center out spring letter reveal">
  <div class="cat-08b__stage">
    <h1 class="cat-08b__word" aria-label="GATHERED">
      <span aria-hidden="true"><i style="--i:3">G</i><i style="--i:2">A</i><i style="--i:1">T</i><i style="--i:0">H</i><i style="--i:0">E</i><i style="--i:1">R</i><i style="--i:2">E</i><i style="--i:3">D</i></span>
    </h1>
    <p class="cat-08b__sub" aria-hidden="true">indices radiate from the middle · linear() spring easing</p>
  </div>
</section>
</div>
.cat-08-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: 100%;
}

.cat-08-group > section {
  flex: 1 1 480px;
  min-width: min(100%,360px);
}

.cat-08a,
.cat-08a *,
.cat-08a *::before,
.cat-08a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-08a {
  --gold: oklch(0.78 0.11 85);
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: #101014;
  font-family: 'Cormorant Garamond',Georgia,serif;
}

.cat-08a__stage {
  text-align: center;
}

.cat-08a__word {
  font-size: clamp(52px,9vw,120px);
  font-weight: 600;
  color: #ece7dd;
  letter-spacing: .14em;
  margin-right: -.14em;
}

.cat-08a__mask {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding: 0 .05em;
}

.cat-08a__mask i {
  display: inline-block;
  font-style: normal;
  transform: translateY(110%) rotate(4deg);
  animation: cat-08a-up 1s cubic-bezier(.19,.8,.22,1) calc(var(--i)*85ms + .3s) both;
}

.cat-08a__tag {
  margin-top: 18px;
  overflow: hidden;
}

.cat-08a__tag span {
  display: inline-block;
  font-family: 'Segoe UI',system-ui,sans-serif;
  font-size: 10.5px;
  letter-spacing: .42em;
  color: var(--gold);
  transform: translateY(120%);
  animation: cat-08a-up .8s cubic-bezier(.19,.8,.22,1) 1s both;
}

@keyframes cat-08a-up {
  to {
    transform: translateY(0) rotate(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .cat-08a__mask i,
    .cat-08a__tag span {
    animation: none;
    transform: none;
  }
}

.cat-08b,
.cat-08b *,
.cat-08b *::before,
.cat-08b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-08b {
  --vio: oklch(0.72 0.19 300);
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: linear-gradient(160deg,#14101f,#0b0912);
  font-family: 'Space Grotesk','Segoe UI',system-ui,sans-serif;
}

.cat-08b__stage {
  text-align: center;
}

.cat-08b__word {
  font-size: clamp(44px,8vw,104px);
  font-weight: 700;
  letter-spacing: .06em;
  margin-right: -.06em;
  color: #f0ecfa;
}

.cat-08b__word i {
  display: inline-block;
  font-style: normal;
  opacity: 0;
  transform: translateY(46px) scale(.7);
  animation: cat-08b-pop .9s calc(var(--i)*110ms + .25s) both;
  animation-timing-function: cubic-bezier(.34,1.56,.64,1);
  animation-timing-function: linear(0,0.004 4.7%,0.147 15.4%,0.581 30.8%,1.09 46%,1.18 55%,1.07 66%,0.96 75%,0.99 87%,1);
}

.cat-08b__word i:nth-child(odd) {
  color: var(--vio);
}

.cat-08b__sub {
  margin-top: 22px;
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11px;
  letter-spacing: .16em;
  color: rgba(200,190,230,.4);
}

@keyframes cat-08b-pop {
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .cat-08b__word i {
    animation: cat-08b-fade .4s ease both;
  }
}

@keyframes cat-08b-fade {
  to {
    opacity: 1;
    transform: none;
  }
}
/* No JavaScript — six letter spans share one keyframe; inline --i indices shift each start by 85ms behind an overflow mask. */

/* No JavaScript — symmetric --i values make letters spring in from the center outward; linear() supplies a real spring curve over a cubic-bezier fallback. */
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Text Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: CSS Staggered Text Letter Reveal Keyframes
Source: https://codefronts.com/motion/css-text-animations/css-fade-up-stagger-text-animation/

Per-letter choreography for luxury brand intros: a masked serif cascade with a gold rule that draws itself, and a center-out spring assemble powered by the modern linear() easing function.
## HTML
```html
<div class="cat-08-group">
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;600;700&display=swap" rel="stylesheet">
<section class="cat-08a" aria-label="Luxury letter cascade reveal">
  <div class="cat-08a__stage">
    <h1 class="cat-08a__word" aria-label="VELOUR">
      <span class="cat-08a__mask" aria-hidden="true"><i style="--i:0">V</i><i style="--i:1">E</i><i style="--i:2">L</i><i style="--i:3">O</i><i style="--i:4">U</i><i style="--i:5">R</i></span>
    </h1>
    <p class="cat-08a__tag" aria-hidden="true"><span>MAISON DE COUTURE — PARIS</span></p>
  </div>
</section>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap" rel="stylesheet">
<section class="cat-08b" aria-label="Center out spring letter reveal">
  <div class="cat-08b__stage">
    <h1 class="cat-08b__word" aria-label="GATHERED">
      <span aria-hidden="true"><i style="--i:3">G</i><i style="--i:2">A</i><i style="--i:1">T</i><i style="--i:0">H</i><i style="--i:0">E</i><i style="--i:1">R</i><i style="--i:2">E</i><i style="--i:3">D</i></span>
    </h1>
    <p class="cat-08b__sub" aria-hidden="true">indices radiate from the middle · linear() spring easing</p>
  </div>
</section>
</div>
```
## CSS
```css
.cat-08-group {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  width: 100%;
}

.cat-08-group > section {
  flex: 1 1 480px;
  min-width: min(100%,360px);
}

.cat-08a,
.cat-08a *,
.cat-08a *::before,
.cat-08a *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-08a {
  --gold: oklch(0.78 0.11 85);
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: #101014;
  font-family: 'Cormorant Garamond',Georgia,serif;
}

.cat-08a__stage {
  text-align: center;
}

.cat-08a__word {
  font-size: clamp(52px,9vw,120px);
  font-weight: 600;
  color: #ece7dd;
  letter-spacing: .14em;
  margin-right: -.14em;
}

.cat-08a__mask {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding: 0 .05em;
}

.cat-08a__mask i {
  display: inline-block;
  font-style: normal;
  transform: translateY(110%) rotate(4deg);
  animation: cat-08a-up 1s cubic-bezier(.19,.8,.22,1) calc(var(--i)*85ms + .3s) both;
}

.cat-08a__tag {
  margin-top: 18px;
  overflow: hidden;
}

.cat-08a__tag span {
  display: inline-block;
  font-family: 'Segoe UI',system-ui,sans-serif;
  font-size: 10.5px;
  letter-spacing: .42em;
  color: var(--gold);
  transform: translateY(120%);
  animation: cat-08a-up .8s cubic-bezier(.19,.8,.22,1) 1s both;
}

@keyframes cat-08a-up {
  to {
    transform: translateY(0) rotate(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .cat-08a__mask i,
    .cat-08a__tag span {
    animation: none;
    transform: none;
  }
}

.cat-08b,
.cat-08b *,
.cat-08b *::before,
.cat-08b *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.cat-08b {
  --vio: oklch(0.72 0.19 300);
  width: 100%;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  background: linear-gradient(160deg,#14101f,#0b0912);
  font-family: 'Space Grotesk','Segoe UI',system-ui,sans-serif;
}

.cat-08b__stage {
  text-align: center;
}

.cat-08b__word {
  font-size: clamp(44px,8vw,104px);
  font-weight: 700;
  letter-spacing: .06em;
  margin-right: -.06em;
  color: #f0ecfa;
}

.cat-08b__word i {
  display: inline-block;
  font-style: normal;
  opacity: 0;
  transform: translateY(46px) scale(.7);
  animation: cat-08b-pop .9s calc(var(--i)*110ms + .25s) both;
  animation-timing-function: cubic-bezier(.34,1.56,.64,1);
  animation-timing-function: linear(0,0.004 4.7%,0.147 15.4%,0.581 30.8%,1.09 46%,1.18 55%,1.07 66%,0.96 75%,0.99 87%,1);
}

.cat-08b__word i:nth-child(odd) {
  color: var(--vio);
}

.cat-08b__sub {
  margin-top: 22px;
  font-family: ui-monospace,Menlo,monospace;
  font-size: 11px;
  letter-spacing: .16em;
  color: rgba(200,190,230,.4);
}

@keyframes cat-08b-pop {
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .cat-08b__word i {
    animation: cat-08b-fade .4s ease both;
  }
}

@keyframes cat-08b-fade {
  to {
    opacity: 1;
    transform: none;
  }
}
```

## JavaScript
```js
/* No JavaScript — six letter spans share one keyframe; inline --i indices shift each start by 85ms behind an overflow mask. */

/* No JavaScript — symmetric --i values make letters spring in from the center outward; linear() supplies a real spring curve over a cubic-bezier fallback. */
```

How this works

Every letter is a span carrying an inline index. One keyframe animates all of them; the index only shifts each letter's start time — that is the whole stagger system:

<i style="--i:3">L</i>
i{animation-delay:calc(var(--i) * 55ms)}

The spring variant sets its indices symmetrically from the middle of the word outward and layers two animation-timing-function declarations — a cubic-bezier() fallback, then a linear(0, 1.18 55%, 0.96 75%, 1) spring for browsers that support it. The heading keeps the readable word in aria-label; the letter spans are decoration.

Make it yours

  • 55ms per letter is stately; 25–35ms feels snappy for product UI.
  • Reverse the cascade by counting --i from the last letter down.
  • Tune the spring: higher first bump in linear() (1.25+) overshoots harder.
  • sibling-index() (Chromium 138+) can replace inline --i entirely — same delay math, zero markup.

Gotchas — read before shipping

  • Split letters by hand or at build time — runtime JS splitting costs CLS and hides copy from crawlers until hydration.
  • Whitespace needs real width: use &nbsp; inside its own span or letters collapse.
  • Keep the readable string on the parent aria-label; per-letter spans read as gibberish to screen readers.

Browser support

ChromeSafariFirefoxEdge
111+15.4+113+111+

Floor set by oklch() as this demo is written. The core technique itself goes back further — Chrome 26+.

Custom-property delays are universal. linear() easing needs Chrome 113+ / Safari 17.2+ / Firefox 112+ and gracefully falls back to the preceding cubic-bezier declaration.

Search CodeFronts

Loading…