A sentence plays one word at a time in the same spot, like a lyric video cut on the beat: small words whisper, the key word lands big in accent. Every word shares one keyframe, offset by its index.
Also known as: kinetic lyric video text, one word at a time animation, rsvp text animation, word flash text css.
<div class="txt-one-word-at-a-time" aria-label="Ideas move at the speed of type."><span aria-hidden="true" style="--i: 0; --s: 1; --x: 0em; --y: 0.3em">Ideas</span><span aria-hidden="true" style="--i: 1; --s: 1; --x: -0.4em; --y: 0em">move</span><span aria-hidden="true" style="--i: 2; --s: 0.7; --x: 0em; --y: -0.3em" class="txt-one-word-at-a-time-soft">at</span><span aria-hidden="true" style="--i: 3; --s: 0.7; --x: 0em; --y: 0.3em" class="txt-one-word-at-a-time-soft">the</span><span aria-hidden="true" style="--i: 4; --s: 1.45; --x: 0.6em; --y: 0em" class="txt-one-word-at-a-time-hot">speed</span><span aria-hidden="true" style="--i: 5; --s: 0.7; --x: 0em; --y: -0.3em" class="txt-one-word-at-a-time-soft">of</span><span aria-hidden="true" style="--i: 6; --s: 1.15; --x: 0em; --y: 0.4em">type.</span></div>
.txt-one-word-at-a-time {
--c: var(--txt-color, #3b82f6);
font-size: calc(var(--txt-size, 32px) * 1.6);
font-family: system-ui, sans-serif;
font-weight: 800;
line-height: 1.2;
display: grid;
place-items: center;
min-width: 5em;
height: 2em;
letter-spacing: -0.03em;}
.txt-one-word-at-a-time span {
grid-area: 1 / 1;
font-size: calc(var(--s) * 1em);
opacity: 0;
animation: txt-one-word-at-a-time-cut 4s infinite;
/* eight beats of 0.5s: seven words, then one beat of silence */
animation-delay: calc(var(--i) * 0.5s - 4s);
}
.txt-one-word-at-a-time-soft { color: color-mix(in srgb, currentColor 55%, transparent); font-weight: 600; }
.txt-one-word-at-a-time-hot {
color: var(--c);
font-style: italic;
font-weight: 900;
}
@keyframes txt-one-word-at-a-time-cut {
0% { opacity: 0; translate: var(--x) var(--y); scale: 1.35; filter: blur(0.06em); animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
3.5% { opacity: 1; translate: 0 0; scale: 1; filter: blur(0); animation-timing-function: linear; }
11% { opacity: 1; translate: 0 0; scale: 0.97; animation-timing-function: ease-in; }
12.5%, 100% { opacity: 0; scale: 0.92; filter: blur(0.04em); }
}
@media (prefers-reduced-motion: reduce) {
.txt-one-word-at-a-time { display: flex; flex-wrap: wrap; justify-content: center; align-items: baseline; gap: 0 0.25em; height: auto; font-size: calc(var(--txt-size, 32px) * 1.1); }
.txt-one-word-at-a-time span { animation: none; opacity: 1; font-size: 1em; }
}
@media (prefers-reduced-motion: reduce) {
.txt-one-word-at-a-time, .txt-one-word-at-a-time *, .txt-one-word-at-a-time::before, .txt-one-word-at-a-time::after, .txt-one-word-at-a-time *::before, .txt-one-word-at-a-time *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--txt-color on the element or a parent.
Currently previewing #3b82f6.--txt-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 40px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.