Back to Code Bytes
3 min read
Texts Reveal

Description

Texts Reveal staggers two lines of copy into view with a combined translate, opacity, and blur animation. The headline lands first; the supporting line follows a beat later, letting the eye settle on the primary message before the secondary context arrives. The exit is intentionally decoupled: every line fades out at the same speed with no Y return, so dismissing the block reads as a single quiet fade rather than a reverse of the entrance.

Example

Welcome aboardLet us set up your workspace.

CSS

:root {
  --stagger-dur: 600ms;
  --stagger-distance: 12px;
  --stagger-stagger: 40ms;
  --stagger-blur: 3px;
  --stagger-ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* Lines start translated down + blurred + invisible; .is-shown
   on the parent flips them to their resting state. The second
   line's transition-delay holds it back by --stagger-stagger
   so the eye lands on the headline first. */
.t-stagger-line {
  display: block;
  opacity: 0;
  transform: translateY(var(--stagger-distance));
  filter: blur(var(--stagger-blur));
  transition:
    opacity var(--stagger-dur) var(--stagger-ease),
    transform var(--stagger-dur) var(--stagger-ease),
    filter var(--stagger-dur) var(--stagger-ease);
  will-change: transform, opacity, filter;
}
.t-stagger-line--2 {
  transition-delay: var(--stagger-stagger);
}

.t-stagger.is-shown .t-stagger-line {
  opacity: 1;
  transform: translateY(0);
  filter: blur(0);
}
/* Exit decouples from the stagger: same fade for every line,
   no Y return, no blur — so the disappearance reads as a
   single quiet fade instead of a reverse reveal. */
.t-stagger.is-hiding .t-stagger-line {
  opacity: 0;
  transform: translateY(0);
  filter: blur(0);
  transition:
    opacity 200ms ease,
    transform 0s linear,
    filter 0s linear;
  transition-delay: 0s;
}

@media (prefers-reduced-motion: reduce) {
  .t-stagger-line {
    transition: none !important;
  }
}

React

import { useEffect, useState } from "react";
import "./texts-reveal.css"; // paste the CSS above

function Reveal() {
  const [shown, setShown] = useState(false);
  useEffect(() => {
    const id = requestAnimationFrame(() => setShown(true));
    return () => cancelAnimationFrame(id);
  }, []);
  return (
    <div className={"t-stagger" + (shown ? " is-shown" : "")}>
      <strong className="t-stagger-line t-stagger-line--1">
        Welcome aboard
      </strong>
      <span className="t-stagger-line t-stagger-line--2">
        Let us set up your workspace.
      </span>
    </div>
  );
}

export function TextsReveal() {
  const [key, setKey] = useState(0);
  return (
    <div>
      <Reveal key={key} />
      <button onClick={() => setKey((k) => k + 1)}>Replay</button>
    </div>
  );
}

Variables

VariableDefaultNotes
--stagger-dur600mssourced from --p18-dur
--stagger-distance12pxsourced from --p18-distance
--stagger-stagger40mssourced from --p18-stagger
--stagger-blur3pxsourced from --p18-blur
--stagger-easecubic-bezier(0.22, 1, 0.36, 1)sourced from --p18-ease

Credit

Adapted from Texts Reveal on transitions.dev by Jakub Antalik. Original source: github.com/Jakubantalik/transitions.dev.