Timeline House
Timeline UI

Timeline

A vertical timeline layout with animated entries that reveal on scroll.

Timeline

A vertical timeline component where each entry animates in from the side as you scroll down the page.

Animation Process

Each timeline entry slides in from alternating sides (left/right) using CSS transforms. The animation is triggered by an IntersectionObserver on each entry element.

"use client";
import { useEffect, useRef } from "react";

const events = [
  { date: "2024-01", title: "Project Kickoff", side: "left" },
  { date: "2024-03", title: "Alpha Release", side: "right" },
  { date: "2024-06", title: "Public Launch", side: "left" },
];

export function Timeline() {
  return (
    <div className="relative">
      <div className="absolute left-1/2 w-px bg-border h-full" />
      {events.map((event, i) => (
        <TimelineEntry key={event.date} event={event} index={i} />
      ))}
    </div>
  );
}

function TimelineEntry({ event, index }: { event: typeof events[0]; index: number }) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          el.classList.add("timeline-visible");
          observer.unobserve(el);
        }
      },
      { threshold: 0.1 }
    );

    observer.observe(el);
    return () => observer.disconnect();
  }, []);

  const isLeft = event.side === "left";

  return (
    <div
      ref={ref}
      className={`relative w-1/2 p-8 opacity-0 transition-all duration-700 ${
        isLeft ? "mr-auto -translate-x-full" : "ml-auto translate-x-full"
      }`}
    >
      <h3>{event.title}</h3>
      <p>{event.date}</p>
    </div>
  );
}

Key steps:

  1. A vertical line runs down the center of the container
  2. Each entry is positioned left or right of the line
  3. Entries start offset (translateX) and invisible
  4. On intersection, they slide into position with a smooth transition

Source Code

View the full implementation on GitHub.