Tailwind UI Pattern Registry for humans and agents

word-by-word stagger headline entrance motion-one translateY clip editorial motion-one

Word Rise

Word Rise

Each word clips behind an overflow-hidden wrapper and translates up from 115% — creates the editorial effect of text rising from below the baseline.

Motion One Scroll
Live Preview

Dependencies

Motion v11.11.13
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Word Rise — Motion Recipe</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script src="https://cdn.jsdelivr.net/npm/motion@11.11.13/dist/motion.js"></script>
  <style>
    .wrise-word {
      display: inline-block;
      transform: translateY(115%);
    }
    @media (prefers-reduced-motion: reduce) {
      .wrise-word { transform: none !important; }
    }
  </style>
</head>
<body class="bg-slate-950 text-slate-200 font-sans">

  <!-- Hero -->
  <div class="mx-auto max-w-3xl px-8 pt-20 pb-32 text-center">
    <span class="mb-6 inline-block rounded-full border border-violet-500/20 bg-violet-500/10 px-3.5 py-1 text-[0.7rem] font-semibold uppercase tracking-widest text-violet-400">Word Rise</span>
    <h1 class="mb-5 text-5xl font-black leading-[1.08] text-slate-100">Words rise<br>from below<br>the baseline.</h1>
    <p class="mx-auto max-w-sm text-base leading-relaxed text-slate-400">Each word is clipped inside a slot and translates up 115% — creating the look of text emerging from beneath the line. Scroll down to see it trigger.</p>
  </div>

  <!-- Section 1 -->
  <div class="wrise-section mx-auto mb-32 max-w-3xl px-8">
    <p class="mb-4 text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">75ms per word</p>
    <h2 class="overflow-hidden text-4xl font-black leading-tight text-slate-100 sm:text-5xl">
      <span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">Craft</span></span>&#32;<span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">the</span></span>&#32;<span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">experience</span></span>&#32;<span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">your</span></span>&#32;<span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">customers</span></span>&#32;<span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">deserve.</span></span>
    </h2>
    <p class="wrise-desc mt-6 max-w-[520px] text-base leading-relaxed text-slate-400">The overflow-hidden wrapper clips each word so it's invisible until it rises into view. No clip-path, no opacity — just a translateY from 115% to 0.</p>
  </div>

  <!-- Section 2 -->
  <div class="wrise-section mx-auto mb-32 max-w-3xl px-8">
    <p class="mb-4 text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">60ms per word — faster rhythm</p>
    <h2 class="overflow-hidden text-4xl font-black leading-tight text-slate-100 sm:text-5xl">
      <span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">Ship</span></span>&#32;<span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">with</span></span>&#32;<span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">clarity.</span></span>&#32;<span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">Move</span></span>&#32;<span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">with</span></span>&#32;<span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">speed.</span></span>
    </h2>
    <p class="wrise-desc mt-6 max-w-[520px] text-base leading-relaxed text-slate-400">Shorten the stagger to 60ms for a crisper, more decisive rhythm. Works well for shorter headlines where all words should feel connected rather than sequential.</p>
  </div>

  <!-- Section 3 — large display -->
  <div class="wrise-section mx-auto mb-24 max-w-3xl px-8">
    <p class="mb-4 text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Large display — 90ms</p>
    <h2 class="text-[clamp(3rem,8vw,5.5rem)] font-black leading-[1.05] tracking-tight text-slate-100">
      <span class="block overflow-hidden leading-[1.1]">
        <span class="wrise-word inline-block">Motion</span>
      </span>
      <span class="block overflow-hidden leading-[1.1]">
        <span class="wrise-word inline-block">done</span>
      </span>
      <span class="block overflow-hidden leading-[1.1]">
        <span class="wrise-word inline-block text-slate-500">right.</span>
      </span>
    </h2>
    <p class="wrise-desc mt-6 max-w-[520px] text-base leading-relaxed text-slate-400">For large display type, wrap each line (not word) in its own slot. The stagger then cascades line by line for a slow, editorial reveal.</p>
  </div>

  <div class="h-16"></div>

  <script>
    var { animate, inView, stagger } = Motion;
    var SPRING = [0.16, 1, 0.3, 1];

    if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
      document.querySelectorAll('.wrise-section:not([data-animated])').forEach(function(section) {
        inView(section, function() {
          section.setAttribute('data-animated', '');

          var words = section.querySelectorAll('.wrise-word');
          var desc  = section.querySelector('.wrise-desc');

          // Words rise from 115% offset — stagger per word
          animate(words, { y: ['115%', '0%'] }, {
            duration: 0.9,
            easing: SPRING,
            delay: stagger(0.075),
          });

          // Description fades after last word
          if (desc) {
            var descDelay = Math.max(0.2, (words.length - 2) * 0.075 + 0.15);
            animate(desc, { opacity: [0, 1], y: [10, 0] }, {
              duration: 0.7,
              easing: 'ease-out',
              delay: descDelay,
            });
          }
        }, { amount: 0.15 });
      });
    }
  </script>
</body>
</html>

The trick is a double-span: an outer inline-block overflow-hidden acts as a clipping slot, and the inner .wrise-word starts at translateY(115%) — just below the slot’s bottom edge, invisible without opacity tricks. When Motion animates it to y: 0, it rises into view exactly like text emerging from beneath the line.

Each word gets its own slot (<span class="inline-block overflow-hidden">), so only the bottom border clips the word. A space entity &#32; between slots preserves word spacing while keeping each slot tight.

The spring easing [0.16, 1, 0.3, 1] overshoots slightly at the top — just enough to feel physical rather than mechanical. At 75ms stagger, a six-word headline finishes in under a second.

For large display type, wrap entire lines in slots instead of individual words. The stagger then cascades line by line for a slower, editorial reveal.

data-animated on the section prevents re-triggering on Astro view transitions. The description paragraph fades in after the last word using a dynamically calculated delay: (wordCount - 2) × staggerInterval + 150ms, giving it a natural pause before appearing.