← Back to gallery
CSS

Elastic Text Custom Easing Tradeoffs

Per-word transform snaps with bounded cubic-bezier overshoot — documents where CSS easing is enough vs. where real spring physics need JS. Three motion types: a vertical rebound, a horizontal slide-in, and a scale snap.

elasticspring-easecustom-easingcubic-bezierper-word-staggertransform-onlyprefers-reduced-motion

Elastic snap · cubic-bezier boundary

Elastic Text Custom Easing

Word-level transforms use a bounded overshoot curve to create elastic text snap without turning the pattern into a JavaScript spring simulation. Three variants demonstrate a tight product badge, a staggered headline, and a subtle toast confirmation.

Tight rebound · short label

Product Badge

A compact badge rebounds into place from below with one visible overshoot. translateY + scale snap, transform isolated to the text run — surrounding layout never recalculates during the cascade.

  • rebound
  • overshoot
  • transform

Word stagger · readable dwell

Headline Snap

Each word slides in horizontally from the left with a soft elastic settle — gentler than a vertical rebound, more cinematic. Word-level spans (not per-character) keep the headline readable during the entrance.

  • horizontal slide
  • word stagger
  • cinematic

Subtle rebound · feedback tone

Toast Confirmation

A small confirmation label pops with scale-only — no translation. Reads as gentle feedback rather than a playful bounce. Safer boundary for production UI where a translating toast in a stack would feel jumpy.

  • scale-only
  • feedback
  • no translate

Elastic inspector

Product Badge

  • rebound
  • overshoot
  • transform

A compact badge rebounds into place from below with one visible overshoot. translateY + scale snap, transform isolated to the text run — surrounding layout never recalculates during the cascade.

Helped you ship something? 🐟 Send my cat a churu

.elastic-phrase {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0 0.4em;
  color: #e0f2fe;
  text-shadow: 0 0 22px rgba(103, 232, 249, 0.14);
}

.elastic-word {
  display: inline-block;
  transform-origin: 50% 100%;
  /* Bounded overshoot via cubic-bezier — no JS spring solver needed. */
  animation: elastic-rebound 2.60s cubic-bezier(0.22, 1.45, 0.36, 1) infinite both;
  animation-delay: calc(var(--elastic-index, 0) * 90ms);
}

@keyframes elastic-rebound {
  /* Vertical bounce-up. transform-origin is bottom so the scale
     punches downward as the word lands. */
  0%   { opacity: 0; transform: translateY(18px) scale(0.92); }
  35%  { opacity: 1; transform: translateY(-5px) scale(1.05); }
  55%  { opacity: 1; transform: translateY(0) scale(1); }
  88%  { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-6px) scale(0.96); }
}

@media (prefers-reduced-motion: reduce) {
  .elastic-word {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

How to make this

Elastic text custom easing uses word-level transform spans with a bounded overshoot cubic-bezier, giving one snap-and-settle motion without a JavaScript spring solver.

html
1<p class="elastic-text-recipe">  <span style="--i: 0">New</span>  <span style="--i: 1">Motion</span></p> <style>7.elastic-text-recipe {  display: flex;  flex-wrap: wrap;  justify-content: center;  gap: 0 .4em;  margin: 0;  color: #e0f2fe;  font: 800 clamp(1.6rem, 7vw, 3.2rem)/1 ui-sans-serif, system-ui;  text-shadow: 0 0 22px rgba(103, 232, 249, .14);}17.elastic-text-recipe span {  display: inline-block;  transform-origin: 50% 100%;  animation:21    elastic-text-recipe-snap 2.6s cubic-bezier(.22,1.45,.36,1) infinite both;22  animation-delay: calc(var(--i) * 90ms);}@keyframes elastic-text-recipe-snap {25  0% { opacity: 0; transform: translateY(1.1em) scale(.92); }  35% { opacity: 1; transform: translateY(-.28em) scale(1.05); }  55%, 88% { opacity: 1; transform: translateY(0) scale(1); }  100% { opacity: 0; transform: translateY(-.35em) scale(.96); }}30@media (prefers-reduced-motion: reduce) {  .elastic-text-recipe span {    animation: none;    opacity: 1;    transform: none;  }}</style>

Annotated snippet

  1. Line 1The words remain real text nodes. Split at word level for readability; per-character elastic motion gets noisy quickly.
    PitfallShould elastic text animate individual letters?

    Usually no for interface copy. Word-level spans preserve readability and reduce element count. Character-level elastic motion is better reserved for short logos or playful one-word treatments.

  2. Line 7Flex with wrapping preserves readable spaces between words. The animation should not depend on absolute positioning to fake line breaks.
  3. Line 17Each word is inline-block so transform can move it independently without affecting the text flow around the phrase.
  4. Line 21The cubic-bezier uses a y value above 1 for a bounded overshoot. It gives one elastic snap without simulating repeated spring rebounds.

    Same 1.5s arrival keyframe (opacity 0 → 1, translateY 18px → 0). linear timing climbs the entire distance at a constant rate — the word slides in flat without feeling like motion you can recognise. cubic-bezier(.22, 1.45, .36, 1) hits y > 1 around 38%, overshoots past the rest position, then settles — one elastic snap that reads as the word arriving.

    PitfallWhen should I use cubic-bezier instead of a spring library?

    Use cubic-bezier for bounded, decorative text snaps that only need one overshoot. Use a spring library when the motion depends on gesture velocity, interruption, chained physics, or repeated rebounds.

  5. Line 22The delay is per word, not per letter. A small stagger makes the phrase feel sequenced while still reading as one message.

    Same cubic-bezier overshoot animation, same delay stride of 70ms. Per-character stagger fires the snap on every letter — eight separate bounces overlap into visual noise. Per-word stagger sends each whole word through one snap, so two clean overshoots arrive in sequence and the phrase stays readable.

    PitfallShould elastic text animate individual letters?

    Usually no for interface copy. Word-level spans preserve readability and reduce element count. Character-level elastic motion is better reserved for short logos or playful one-word treatments.

  6. Line 25The keyframe overshoots once and settles by the middle of the cycle. Repeated rebounds belong in a real spring system, not a looping CSS label.
    PitfallWhen should I use cubic-bezier instead of a spring library?

    Use cubic-bezier for bounded, decorative text snaps that only need one overshoot. Use a spring library when the motion depends on gesture velocity, interruption, chained physics, or repeated rebounds.

  7. Line 30Reduced motion removes the snap and leaves all words visible. Do not replace reduced motion with a different jumpy transition.
    PitfallHow should reduced motion work for elastic text?

    Skip the overshoot and show the final text immediately. A subtle opacity change can be acceptable, but avoid replacing translate motion with scale pops for users who requested less motion.

Other pitfalls

Can cubic-bezier values go above 1?
Yes. Control point y values above 1 or below 0 can create overshoot. Keep the curve bounded and test the result; extreme values can feel rubbery or make text harder to read.
Is elastic text animation performant?
Transform and opacity on a few words are cheap. Performance drops with long text, blur filters, layout-changing properties, or many looping phrases on the same page.

Notes

Overview

Per-word elastic snap reveal uses bounded cubic-bezier() overshoot to simulate a spring without real physics. The pattern documents where CSS easing curves are honest enough (one bounce, then settle) vs. where you need real spring math (multi-bounce decays, velocity-dependent damping — those want JS).

When to use it

Reach for elastic snaps on word-by-word hero reveals, product callouts, and any short string where one playful bounce per word reads as personality. Skip it for content users read multiple times — the bounce stops being cute by the third re-read. Skip it for forms or controls; a bouncy submit button looks unserious.

How it works

Each word is wrapped in a <span> with transform: translateY(0.5em) and opacity: 0 at rest. A keyframe animates them to translateY(0) + opacity: 1 with an overshoot easing like cubic-bezier(.34, 1.56, .64, 1). The critical magic is the bezier’s second control point (Y > 1) which pushes the value past 100% before settling — that overshoot reads as the spring. Per-word animation-delay creates the stagger. For multi-bounce springs that CSS cannot fake well, switch to Web Animations API with a longer keyframe array.

Production gotchas

Single-bounce CSS overshoots feel honest; multi-bounce decays require either a precise multi-stop keyframe array or a real physics library (e.g. react-spring, Motion One) — do not try to fake multi-bounce with a single cubic-bezier or the curve oscillates unnaturally. The overshoot pushes content vertically past adjacent layout; check that no sibling element gets visually clipped. Per-word spans can leak white-space between siblings on layout engines that strip inline whitespace differently — use display: inline-block + explicit space characters between spans.

Accessibility

Wrap the parent with aria-label holding the full sentence so the per-word spans do not split the announcement. Under prefers-reduced-motion: reduce drop the overshoot keyframe and the per-word delay so the entire phrase appears simultaneously without bounce. Vestibular users find overshoot motion uncomfortable — this is one of the strongest cases for honoring the media query.

References

Implementation depth

Elastic text should use transform-only motion so the words bounce without pushing surrounding layout. Custom cubic-bezier curves can imply spring behavior without a JavaScript physics loop.

The overshoot is the taste decision. Too much rebound feels playful but can hurt readability, especially in product UI. Reduced motion should remove the bounce while preserving the final emphasis or staggered order.