Split word reveal
A heading rises word by word from behind a mask. Each word is wrapped by JS so the HTML stays a normal heading.
CSSJavaScriptIntermediate
<h1 id="h">Words that rise into place</h1>
h1{margin:0;max-width:11ch;text-align:center;font-size:clamp(2.4rem,9vw,4.6rem);line-height:1.02;letter-spacing:-.035em}
.mask{display:inline-block;overflow:hidden;vertical-align:top;padding-bottom:.12em}
.word{display:inline-block;transform:translateY(110%);animation:rise 1s cubic-bezier(.2,.8,.2,1) forwards;animation-delay:calc(var(--i) * 90ms + 200ms)}
@keyframes rise{to{transform:none}}
@media (prefers-reduced-motion:reduce){.word{transform:none;animation:none}}
const h = document.getElementById('h');
const text = h.textContent;
h.setAttribute('aria-label', text);
h.innerHTML = text.split(' ').map((w, i) =>
`<span class="mask" aria-hidden="true"><span class="word" style="--i:${i}">${w}</span></span>`).join(' ');
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Split word reveal</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{display:grid;place-items:center;background:#140807;color:#fffffc;font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
h1{margin:0;max-width:11ch;text-align:center;font-size:clamp(2.4rem,9vw,4.6rem);line-height:1.02;letter-spacing:-.035em}
.mask{display:inline-block;overflow:hidden;vertical-align:top;padding-bottom:.12em}
.word{display:inline-block;transform:translateY(110%);animation:rise 1s cubic-bezier(.2,.8,.2,1) forwards;animation-delay:calc(var(--i) * 90ms + 200ms)}
@keyframes rise{to{transform:none}}
@media (prefers-reduced-motion:reduce){.word{transform:none;animation:none}}
</style></head>
<body>
<h1 id="h">Words that rise into place</h1>
<script>
const h = document.getElementById('h');
const text = h.textContent;
h.setAttribute('aria-label', text);
h.innerHTML = text.split(' ').map((w, i) =>
`<span class="mask" aria-hidden="true"><span class="word" style="--i:${i}">${w}</span></span>`).join(' ');
</script>
</body></html>
Good for
Hero and section headings on editorial, agency and portfolio sites.
Watch out for
Keep aria-label on the heading with the full sentence, and mark the split spans aria-hidden. Add a little bottom padding to the mask so descenders are not clipped.
Accessibility
Wrap motion in @media (prefers-reduced-motion: reduce) and switch it off or shorten it for people who ask for less movement.