Liquid Stretch Text
Each letter stretches like hot caramel on hover and snaps back with an elastic spring return — pure CSS, zero dependency.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Text has 56 effects, including 4 free. Explore the category →
3 usage examples



How it works
The text is split into as many <span class="char"> elements as there are characters — each holds the letter as both text content and a data-char attribute. The critical property is display: inline-block on each .char: it isolates each letter's transform from its neighbors, unlike a native inline element. The split can be done manually in HTML or via the splitChars(el) utility function exposed by the script, which reads data-text or the element's textContent.
On hover, the .kt-liquid .char:hover rule applies transform: scaleY(1.5) scaleX(.8) — the letter grows 50% taller while compressing 20% horizontally, mimicking the behavior of an incompressible fluid being pulled (caramel, taffy). Simultaneously, color shifts to #a78bfa (violet) and filter: blur(0.5px) adds a subtle optical diffusion at peak deformation.
The elastic snap-back on mouse-out is produced entirely by the CSS transition: transform 0.4s cubic-bezier(.34, 1.56, .64, 1). The Bézier curve with its 2nd control point at y = 1.56 (above 1) causes the animated value to overshoot its target on return — the letter briefly crosses scaleY(1) downward (a slight squish) before settling, producing the characteristic elastic snap. No RAF or JS physics engine is involved.
The animation is 100% CSS. The only JS responsibility is the utility function splitChars(), which is not called automatically — the HTML can pre-split chars statically, making the effect compatible with any server rendering pipeline, SSG, or CSS-in-JS setup without client-side JS execution.
Accessibility
- prefers-reduced-motion not handled in the provided code: the CSS transition fires on all OSes without checking the system preference. To meet WCAG 2.3.3, add to your stylesheet:
@media (prefers-reduced-motion: reduce) { .kt-liquid .char { transition: none; } }. - Text content lives in real DOM text nodes (
<span>inline-block) — screen readers read the letters normally; noaria-hiddenis needed on the char spans. - No
roleoraria-labelis defined on the container in the base code: if the text carries semantic value (heading, label), wrap it in the appropriate semantic element (<h1>,<p>) and add anaria-labelwith the full text to prevent screen readers from reading letters individually. - The effect is triggered by CSS
:hoveronly — no:focusequivalent is defined. Keyboard users do not see the deformation. Add.kt-liquid .char:focus { … }(or:focus-withinon the parent) to cover keyboard accessibility. user-select: noneon.kt-textprevents text selection. Remove this rule if the text is meant to be copyable (links, page headings).
Browser compatibility
Requires CSS Transforms level 1 and CSS Transitions — native in all modern browsers since 2017. Zero JS dependency for rendering.
Without CSS Transforms support, text displays normally with its font and color — no errors, no broken scripts. On mobile (touch, no hover), letters do not interact; a <code>touchstart</code> JS listener can simulate hover if needed.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="kt-text kt-liquid" data-text="HELLO">
<!-- One .char span per letter — generated by splitChars(el) or pre-split -->
<span class="char" data-char="H">H</span>
<span class="char" data-char="E">E</span>
<span class="char" data-char="L">L</span>
<span class="char" data-char="L">L</span>
<span class="char" data-char="O">O</span>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| font-size on .kt-text | 2.2rem | Typography size. Increase to 4–8 rem to amplify the caramel effect — it reads far more dramatically at large scale. |
| font-weight on .kt-text | 800 | Font weight. Condensed or extra-bold fonts (900) increase the contrast between normal and stretched states. |
| scaleY in .kt-liquid .char:hover | 1.5 | Vertical stretch factor on hover. 1.2 = subtle effect; 1.8–2.0 = pronounced caramel-like stretch. |
| scaleX in .kt-liquid .char:hover | 0.8 | Horizontal compression on hover. Drop to 0.6 to accentuate liquid incompressibility; raise to 0.95 for vertical-only stretch. |
| color in .kt-liquid .char:hover | #a78bfa | Letter color at peak deformation. Replace with your brand color or use currentColor to inherit from the parent. |
| blur() in filter: blur() of .kt-liquid .char:hover | 0.5px | Optical diffusion on hover. Set to 0 to disable; 1–2 px for a softer, more liquid look. |
| transition-duration in .kt-liquid .char | 0.4s | Duration of the elastic return. 0.2–0.25 s = snappy; 0.6–0.8 s = slow, viscous bounce. |
| cubic-bezier(.34, 1.56, .64, 1) in transition | .34, 1.56, .64, 1 | Spring curve. Increase the 2nd parameter (1.56 →) for a more pronounced overshoot; bring it close to 1.0 for a return without spring. |
FAQ
:hover + transition). The JS only exposes splitChars(el), a utility function not called automatically. If the HTML already contains the <span class="char"> elements, no JS runs.cubic-bezier(.34, 1.56, .64, 1) curve in the transition property controls the overshoot. The 2nd parameter (1.56) exceeds 1.0 — that's what produces the snap. Raise it (e.g. 2.0) for a bouncier spring, bring it down to 1.0 for a return with no bounce. Preview the curve at cubic-bezier.com.kt-text kt-liquid whose children have the class char. Call splitChars(el) on each container for dynamic splitting; instances are fully independent.