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.
Dependencies
<!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> <span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">the</span></span> <span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">experience</span></span> <span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">your</span></span> <span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">customers</span></span> <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> <span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">with</span></span> <span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">clarity.</span></span> <span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">Move</span></span> <span class="inline-block overflow-hidden leading-[1.15]"><span class="wrise-word">with</span></span> <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   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.