Text Morph
Two stacked words taking turns every 2.5 s in the same spot: the outgoing one blurs to 12 px, shrinks and drops, while the next comes into focus — a blurred crossfade in a violet-cyan gradient, with no stray letters. CSS and 1.4 KB of JavaScript, word list in the HTML, a plain fade when the user reduces motion.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Liquid has 31 effects, including 6 free. Explore the category →
3 usage examples



How it works
The .text-morph-container (60 px tall, content centered with flex) holds two absolutely positioned <span class="text-morph-word">s, so they sit exactly on top of each other. The text is painted with a violet to cyan gradient (#a855f7 → #06b6d4) clipped to the letter shapes (background-clip: text and -webkit-text-fill-color: transparent), at 2.5rem, weight 900. Two classes describe the two states: .text-morph-in (opaque, sharp, normal size) and .text-morph-out (opacity: 0, filter: blur(12px), scale(.9) and 8 px lower). A single 0.6 s transition on opacity, blur and transform does all the work.
The script (1.4 KB) handles every .text-morph-container on the page. Every 2.5 s (data-interval attribute, in milliseconds), it writes the next word into the hidden span, then swaps the two classes: the visible word blurs out while the other comes in, in the same spot. The list comes from the container's data-words attribute, comma-separated, read again at each swap: you can change it at any time from the page. The first word is the one written in the first span. Measured in Chromium 151, Firefox 153 and WebKit 26 over 11 s: Design, Creative, Modern, Elegant, Fluid, in order, no word skipped.
The two words never move: the container keeps its height, the lines above and below do not jump, even when the new word is longer. It has no width of its own (both words are absolutely positioned): alone on its line it centers itself; inside a sentence, give it a width or the whole line, as in the examples. The timer does nothing while the block is off screen (IntersectionObserver) or the tab is hidden. The state classes are prefixed (text-morph-in, text-morph-out): a .hidden or .visible class from your framework will not hide a word.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, only opacity is animated: the outgoing word fades without blurring, shrinking or dropping, and the next one fades in at the same spot. Measured in all three engines:filter: noneandtransform: noneon the hidden word, swaps still in order. - Screen readers: the hidden word carries
aria-hidden="true", moved at each swap: a screen reader reads a single word, the one displayed. The change is not announced (noaria-liveregion), which avoids an announcement every 2.5 s. If the list matters (the jobs on offer, today's produce), also give it as text elsewhere on the page. - Continuous motion: the swap runs forever. On a page people read for a long time, provide a way to stop it or limit it to a few rounds (WCAG criterion 2.2.2 for content that moves for more than 5 s).
Browser compatibility
CSS (opacity, filter and transform transitions, background-clip: text) + 1.4 KB of JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26: same word order, same blurred crossfade, same plain fade under reduced motion, off-screen pause checked in Chromium and Firefox. The gradient text is written as separate properties (-webkit-background-clip then background-clip): the short form background: … text is ignored by older browsers, which would leave the text transparent.
Without JavaScript, the first word stays on screen, sharp and readable; the second stays hidden. Without filter (a rare case), the words take turns with a fade and a slight slide, no blur. Without IntersectionObserver, the swap also runs off screen.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0405-root">
<div class="text-morph-container" data-words="Design, Creative, Modern, Elegant, Fluid">
<span class="text-morph-word text-morph-in">Design</span>
<span class="text-morph-word text-morph-out" aria-hidden="true">Creative</span>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Word list (HTML — data-words on .text-morph-container) |
Design, Creative, Modern, Elegant, Fluid |
Words or phrases separated by commas. Write the first one in the first span. The list is read again at each swap: change the attribute from your script to follow a language or a stock. |
Pace (HTML — data-interval, in milliseconds) |
2500 | 4000 for a screen read from a distance; below 1500, the word does not have time to be read before it leaves (the transition lasts 0.6 s). |
Blur and movement (CSS — .text-morph-word.text-morph-out) |
blur(12px), scale(.9) translateY(8px) |
blur(20px) gives a hazier exit; transform: none keeps a blurred fade in place. |
Fade length (CSS — transition: opacity .6s, filter .6s, transform .6s) |
0.6 s | 1 s for a very soft swap; keep it clearly shorter than the pace. |
Text gradient (CSS — background of .text-morph-word) |
linear-gradient(135deg, #a855f7, #06b6d4) |
Any color or gradient; for plain text, remove the gradient and -webkit-text-fill-color, then set color. |
Size (CSS — font-size of .text-morph-word, container height) |
2.5rem, 60 px container | Match the container's height to the word size; for long names, lower the size and add white-space: nowrap. |
FAQ
display: inline-flex and give it the width of the longest word (width: 7ch for instance): the sentence will not move from one word to the next.data-words in each version of the page. Since the list is read again at each swap, you can also replace it live (container.dataset.words = 'Hello, Hola, Ciao'): the next word shown comes from the new list.