Process Timeline

Numbered steps with a gradient line that draws as you scroll.

How it works

From setup to insight in minutes

  1. 01

    Connect your data

    5 min

    Link your database, warehouse or product events in minutes with guided setup.

  2. 02

    Define what matters

    10 min

    Choose the metrics and goals your team cares about. We build the dashboards.

  3. 03

    Invite your team

    2 min

    Share live views, leave comments and get alerts in the tools you already use.

  4. 04

    Ship with confidence

    Ongoing

    Measure every launch and see exactly what moved the numbers.

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/process-timeline.tsx

    components/ui/process-timeline.tsx
    "use client";
    
    import { motion } from "motion/react";
    
    type Step = { title: string; text: string; tag?: string };
    
    const steps: Step[] = [
      { title: "Connect your data", text: "Link your database, warehouse or product events in minutes with guided setup.", tag: "5 min" },
      { title: "Define what matters", text: "Choose the metrics and goals your team cares about. We build the dashboards.", tag: "10 min" },
      { title: "Invite your team", text: "Share live views, leave comments and get alerts in the tools you already use.", tag: "2 min" },
      { title: "Ship with confidence", text: "Measure every launch and see exactly what moved the numbers.", tag: "Ongoing" },
    ];
    
    export function ProcessTimeline({ items = steps }: { items?: Step[] }) {
      return (
        <ol className="relative">
          <motion.span
            aria-hidden
            className="absolute left-[19px] top-2 bottom-2 w-px origin-top bg-gradient-to-b from-blue-600 via-blue-400 to-zinc-200"
            initial={{ scaleY: 0 }}
            whileInView={{ scaleY: 1 }}
            viewport={{ once: true, amount: 0.3 }}
            transition={{ duration: 1.4, ease: [0.22, 1, 0.36, 1] }}
          />
          {items.map((step, i) => (
            <motion.li
              key={step.title}
              className="relative flex gap-6 pb-10 last:pb-0"
              initial={{ opacity: 0, x: -16 }}
              whileInView={{ opacity: 1, x: 0 }}
              viewport={{ once: true, amount: 0.5 }}
              transition={{ duration: 0.6, delay: 0.15 + i * 0.15, ease: [0.22, 1, 0.36, 1] }}
            >
              <span className="relative z-10 grid size-10 shrink-0 place-items-center rounded-full border border-zinc-200 bg-white text-sm font-semibold text-zinc-900 shadow-sm">
                {String(i + 1).padStart(2, "0")}
              </span>
              <div className="pt-1.5">
                <div className="flex flex-wrap items-center gap-2">
                  <h3 className="font-semibold text-zinc-900">{step.title}</h3>
                  {step.tag && <span className="rounded-full bg-zinc-100 px-2 py-0.5 text-xs text-zinc-500">{step.tag}</span>}
                </div>
                <p className="mt-1.5 max-w-md text-sm leading-relaxed text-zinc-500">{step.text}</p>
              </div>
            </motion.li>
          ))}
        </ol>
      );
    }
    
    export default function Demo() {
      return (
        <div className="w-full max-w-xl">
          <p className="text-sm font-medium text-blue-600">How it works</p>
          <h2 className="mb-10 mt-2 text-3xl font-semibold tracking-tight text-zinc-900">From setup to insight in minutes</h2>
          <ProcessTimeline />
        </div>
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.