Back to library
INTERACTION_134

Timeline Progression.

A vertical line draws down and reveals each milestone. The line scaled to 0 on the Y axis, rows at 0 opacity before the trigger; drawing the line with progress and revealing each row as the line passes it once scroll progress through the timeline.

CATEGORY
Scroll
COMPLEXITY
Intermediate
TECH
CSS
PLATFORM
Both
PERFORMANCE
Light
LIVE PLAYGROUND
CSS / CSS
/* Timeline Progression — A vertical line draws down and reveals each milestone. */
.timeline-progression {
  --dur: scroll-linked;
  --ease: linear;
  --stagger: 60ms;
  will-change: transform, opacity;
}

.timeline-progression > * {
  opacity: 0;
  transform: translateY(14px);
  animation: timeline-progression-in var(--dur) var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}

/* the line scaled to 0 on the Y axis, rows at 0 opacity -> drawing the line with progress and revealing each row as the line passes it */
@keyframes timeline-progression-in {
  to {
    opacity: 1;
    transform: none;
    filter: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .timeline-progression > * {
    opacity: 1;
    transform: none;
    animation: none;
  }
}
React / TSX
import { useEffect, useRef } from "react";

/** Timeline Progression — trigger: scroll progress through the timeline */
export function TimelineProgression({ children }: { children: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      el.dataset["state"] = "in";
      return;
    }
    const io = new IntersectionObserver(
      ([entry]) => {
        if (entry?.isIntersecting) {
          el.dataset["state"] = "in";
          io.unobserve(el);
        }
      },
      { threshold: 0.2 },
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);

  return (
    <div ref={ref} className="timeline-progression" data-state="out">
      {children}
    </div>
  );
}

IF YOU LIKE THIS, STEAL THESE TOO.

INTERACTION_127CSS

Sticky Storytelling Section

A sticky visual holds while the copy beside it advances.

Scroll / LightView
INTERACTION_135CSS

Zoom Through Section

The section scales up and passes the viewer as they scroll.

Scroll / MediumView
INTERACTION_136CSS

Stacked Scroll Panels

Sections stack over one another like cards on a table.

Scroll / LightView
INTERACTION_015CSS + JS

Scroll Reveal

Content fades and rises as it enters the viewport.

Scroll / LightView