Skip to content
ARDENT / MOTION LAB
Library

Typography · Starter

Staggered Line Typography

An editorial composition with alternating indents and italic accents. Each line is masked and revealed with a 120ms stagger.

Live preview

Plays on load. Use Replay to watch the cadence again.

Copy-ready · type-checkedThe preview above renders this exact file. Reduced-motion fallback included.
/**
 * Staggered Typography
 * Four offset lines reveal with a measured editorial cadence.
 * Requires: motion, Tailwind with shadcn semantic tokens.
 */
import { useState } from "react";
import { motion, useReducedMotion } from "motion/react";

export default function StaggeredTypography({
  reduced: reducedProp,
  intensity = 1,
}: {
  reduced?: boolean;
  intensity?: number;
}) {
  const systemReduced = useReducedMotion();
  const reduced = reducedProp ?? systemReduced ?? false;
  const amount = Math.min(1.5, Math.max(0.5, intensity));
  const [replay, setReplay] = useState(0);
  const lines = ["We design", "the moment", "between", "intent & action."];

  return (
    <section className="flex min-h-[80vh] items-center justify-center bg-background px-6 py-20 text-foreground">
      <div className="w-full max-w-5xl">
        <p className="mb-10 font-mono text-xs uppercase tracking-[0.2em] text-muted-foreground">
          A practice of precision / 02
        </p>
        <h2 key={replay} className="font-serif tracking-tight">
          {lines.map((line, index) => (
            <span key={line} className="block overflow-hidden border-b border-border py-3">
              <motion.span
                className={`block text-[clamp(2.5rem,8vw,7rem)] leading-[1.1] ${index % 2 ? "pl-[8%] italic" : ""}`}
                initial={reduced ? false : { y: `${110 * amount}%`, opacity: 0 }}
                animate={{ y: 0, opacity: 1 }}
                transition={{
                  duration: reduced ? 0 : 1,
                  delay: reduced ? 0 : index * 0.13,
                  ease: [0.16, 1, 0.3, 1],
                }}
              >
                {line}
              </motion.span>
            </span>
          ))}
        </h2>
        <div className="mt-8 flex items-center justify-between gap-6">
          <p className="text-sm text-muted-foreground">Every line has its moment.</p>
          <button
            type="button"
            onClick={() => setReplay((value) => value + 1)}
            className="rounded-full border border-border px-5 py-3 text-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4"
          >
            Replay
          </button>
        </div>
      </div>
    </section>
  );
}
Next patternMagnetic Button→