Split Text by Length, Not by Habit
MUST: Choose the split unit from the string length, not by reflex: per-character only under ~40 chars (22–46ms stagger), per-word beyond that (70–95ms), per-line for paragraphs (90–120ms). Total reveal = stagger × unit count, so the count is the variable that matters. Do not fix a long per-character cascade by clamping the delay — that collapses the tail into a flash. Preserve spaces: a gap that ends up inside an inline-block shard is collapsed away, so set white-space: pre-wrap on the wrapper or emit the space as a text node between tokens.
Pick per-character, per-word, or per-line from the string length — a per-letter cascade on a long headline is still arriving after the reader has finished it
// bad — 45 chars x 60ms = last letter starts at 2.6s
text.split("").map((c, i) => <span style={{ animationDelay: `${i * 60}ms` }}>{c}</span>)
// good — 8 words x 70ms, cascade intact, done in 1.2s, gaps outside the shards
text.split(" ").map((w, i, all) => (
<Fragment key={i}>
<span className="inline-block" style={{ animationDelay: `${i * 70}ms` }}>{w}</span>
{i < all.length - 1 ? " " : null}
</Fragment>
))Bad
Good
Why it matters
The default move — split on `''`, map, multiply the index by a delay — is correct for "Hello" and wrong for a sentence, because total reveal time is stagger × unit COUNT, and only the count changes. Pixel Point's catalog prices this by unit: per-character effects run a 22–46ms stagger, per-word 70–95ms, per-line 90–120ms. The stagger goes UP as the unit gets bigger precisely because there are fewer of them, so every family lands in roughly the same total.
Run soft-blur-in's 25ms across a 62-character headline and the last glyph does not begin until 1550ms, then takes its own 900ms to finish: 2.4 seconds of a title assembling itself while the reader has already read it and scrolled. Split the same string per word — eleven units at 70ms — and the cascade completes in 770ms with its rhythm intact. IMPORTANT — this is NOT animations-lottie-stagger-budget in different words.
That rule clamps the delay (`delay = Math.min(i * step, MAX)`), which is the right fix for a list of unknown length; applied to text it is the WRONG fix, because clamping collapses the tail of the cascade so the last fourteen letters all fire on the same frame — a flash glued to the end of a stagger. For text you change the unit, not the ceiling. Whichever unit you pick, spaces must survive the split, and neither of the obvious ways works by default: a space that becomes its own `inline-block` shard, or that trails inside a word token, is collapsed away and the headline silently loses its word gaps. Either set `white-space: pre-wrap` on the wrapper, or emit the gap as a text node BETWEEN the animated tokens.