You can build a text reveal animation in Webflow that’s reusable, scalable, and easy to apply — to a single-line heading, a multi-line heading, or a button on hover — with a single action inside the animation timeline. The trick is to stop fighting Webflow’s native elements and reach for one you might have overlooked: the Custom Element.
I’ll start with the “old way” first, because it’s the version most people build, and it’s worth seeing exactly where it falls apart. Then I’ll rebuild the same effect the new way. When I timed myself, the old way took over six minutes per heading; the new way took under four — and, more importantly, the new one scales to longer and multi-line headings with almost no extra work.
The whole idea rests on a small shift in thinking: let a CSS transition do the animating, and let the Custom Element carry the per-letter timing as inline CSS. Once that clicks, the Webflow timeline shrinks to a single action.
How it works
The old way, and why it hurts. The classic setup masks a heading (overflow: hidden), splits it into one span per letter, sets each span to display: inline-block so it can take a transform, then builds a scroll-into-view interaction that moves every letter up with a hand-tuned delay. It works, but each letter needs its own action, longer headings force you back into the timeline to add more, and native Webflow headings can’t do multi-line reveals at all without an accessibility-hurting hack. There’s also a Webflow quirk worth knowing: two actions created from the same span get linked to the same target, so you build the initial condition from the last span to keep them independent.
The Custom Element. A Custom Element is a placeholder you drop on the canvas (Ctrl/Cmd + E → “custom element”) and then assign any HTML tag to via the settings panel. Set its Tag to h2 and it is an h2. Set another’s Tag to span and it becomes a span you can also give a Text value to. Crucially, a Custom Element accepts any custom attribute — including style, which lets you inject inline CSS. That single capability is what makes the new approach possible.
Why the animation shrinks to one action. In the new setup, the shared span class carries a transform transition (700ms, ease-out-cubic). Webflow animations act on CSS properties, so when your timeline changes a property that already has a matching transition, the transition does the animating — you only specify the end value, not a duration or easing. The stagger, meanwhile, comes from a transition-delay set individually on each span through its inline style attribute (0, 100, 200… milliseconds). So the timeline needs just an initial condition (push the span wrapper down to hide the letters) and one action (move the letters up by -100%). Everything else is CSS.
How to use it
-
Build the heading with Custom Elements. Add a Custom Element and set its Tag to
h2. Inside it, add adivblock as a heading line and give thatoverflow: hidden(it’s the mask). Inside the line, add anotherdivas the span wrapper. Inside the wrapper, add a Custom Element per letter (and per blank space), setting each one’s Tag tospanand its Text to the letter it represents. -
Style the spans once. Apply a single base class to every span with
display: inline-block(so transforms apply) and a transform transition of 700ms, ease-out-cubic. No combo classes per letter this time — one shared class is enough. -
Add the per-letter timing as inline CSS. On each span’s
styleattribute, settransition-delayto a stepped value —0ms,100ms,200ms, and so on. This is the stagger, and it lives entirely outside the timeline. -
Build the one-action animation. Select the heading, add a Scroll into view interaction. Initial condition: move the span wrapper down by
100%(it hides below the overflow-hidden line). Then one action: move the spans up by-100%. No duration, no delay, no easing on the action itself — the transition and the inline delays handle all of it. -
Extend to multi-line. Duplicate the heading line (wrapper + spans) for each additional line, and reuse the same animation untouched. Only adjust the
transition-delayvalues — e.g. offset the second line’s letters by50msso the two lines cascade. -
Adapt for a button-on-hover reveal. Give the button’s text wrapper
overflow: hiddenandposition: relative. Put two copies of the text inside; set the second toposition: absolutewith atopoffset of100%so it starts hidden below. Split both into delayed spans as above, then add a hover interaction with a single action moving the spans up by-100%, and a hover-out action returning them to0%.
Note: the exact per-letter markup (every span’s Text value and the full transition-delay sequence for each heading) is set element by element in the Designer rather than pasted as code, so the fastest way to see the precise structure is to clone the project above and inspect it.