Experiments

Letter Cascade

A simple text reveal animation made entirely with CSS

November 2025

Move the dials, then restart it

This is a per-letter reveal animation I built entirely with CSS - no JavaScript timing loop, no animation library, just a single custom property doing the heavy lifting.

Each letter gets its own --index, set inline via style={{ '--index': i }} on the span. That value feeds animation-delay: calc(var(--stagger) * var(--index)), so instead of every letter starting at once, each one waits a little longer than the last - at the 0.03s stagger it ships with, letter 0 starts immediately and letter 9 starts 270ms later. That’s what gives you the cascade instead of a flat pop-in.

One detail I almost missed: animation-fill-mode: backwards. Without it, every letter flashes into its final position for a frame before its delay kicks in, which looks broken. With backwards, each letter just holds still - translated out of view - until its turn comes up. The easing, a cubic-bezier(.075, .82, .165, 1) curve, slows down hard right at the end so it settles instead of overshooting and bouncing.

LetterCascade.tsx

import styles from './styles.module.css'
const TEXT = 'Animations'
export function LetterCascade() {
return (
<h1 aria-label={TEXT} className={styles.h1}>
{[...TEXT].map((letter, index) => (
<span
aria-hidden={'true'}
key={`${index}-${letter}`}
className={styles.letter}
style={{ '--index': index }}
>
{letter}
</span>
))}
</h1>
)
}

styles.module.css

.h1 {
font-size: clamp(32px, 5vw, 48px);
font-weight: 600;
letter-spacing: -0.05em;
overflow: hidden;
color: #0a0a0a;
}
.letter {
display: inline-block;
animation: yTranslate 1.3s cubic-bezier(.075, .82, .165, 1);
animation-delay: calc(0.03s * var(--index));
animation-fill-mode: backwards;
}
@keyframes yTranslate {
from {
transform: translateY(100%)
}
to {
transform: translateY(0);
}
}
@media (prefers-color-scheme: dark) {
.h1 {
color: #fafafa;
}
}
@media (prefers-reduced-motion: reduce) {
.letter {
animation: none;
}
}

It also respects prefers-reduced-motion - if you’ve told your OS you’d rather skip animations, you just get the final text straight away, no cascade, no fuss.

That's the last one

Back to all experiments

Written by Pablo Cingolani, a Design Engineer at RoomPriceGenie. Say hello at hello@cingopablo.com.