Skip to content
All Taste components

Animated app UI

Order tracking timeline

A delivery timeline that fills from the first step down to where the parcel is now, with the live step pulsing.

5 steps · done / live / upcoming · line fill · courier progress · status badge

Order #SX-20418

Arriving Thursday, 17 Oct

In transit
  1. OrderedPaid by card
  2. PackedBlue Pine Supply, Pune
  3. ShippedHanded to courier
  4. Out for deliveryWith Ravi, 6 stops away
  5. DeliveredBy 6 pm today
Ravi is on the way Koregaon Park · about 40 min
import { Check, MapPin, Truck } from "lucide-react";
import { cn } from "@/lib/utils";

/* Add once to globals.css:
@keyframes fill-down { from { transform: scaleY(0); } }
@keyframes breathe { from { transform: scale(1); opacity: .6; } to { transform: scale(1.35); opacity: .16; } }
*/

const steps = [
  { title: "Ordered", text: "Paid by card", time: "Mon 10:12" },
  { title: "Packed", text: "Blue Pine Supply, Pune", time: "Mon 16:40" },
  { title: "Shipped", text: "Handed to courier", time: "Tue 09:05" },
  { title: "Out for delivery", text: "With Ravi, 6 stops away", time: "Thu 08:30" },
  { title: "Delivered", text: "By 6 pm today", time: "" },
];
const current = 3;
const STEP = 0.38; // seconds per step — the line reaches each dot as it pops

export function OrderTracking() {
  return (
    <section aria-labelledby="order-title" className="mx-auto grid max-w-[440px] gap-[18px] rounded-lg border border-border bg-bg p-5 font-sans">
      <div className="flex flex-wrap items-start justify-between gap-3">
        <div>
          <p className="text-xs tabular-nums text-fg-subtle">Order #SX-20418</p>
          <h2 id="order-title" className="text-[17px] font-medium text-fg">Arriving Thursday, 17 Oct</h2>
        </div>
        <span className="inline-flex items-center gap-1.5 rounded-full bg-info-soft px-2 py-0.5 text-xs font-medium text-info-fg">
          <span className="size-1.5 animate-pulse rounded-full bg-current motion-reduce:animate-none" />In transit
        </span>
      </div>

      <ol className="grid">
        {steps.map((s, i) => {
          const done = i < current, now = i === current;
          return (
            <li key={s.title} aria-current={now ? "step" : undefined} className="relative grid grid-cols-[24px_1fr_auto] gap-x-3 pb-5 last:pb-0">
              {i < steps.length - 1 && (
                <span aria-hidden className="absolute bottom-0.5 left-[11px] top-[26px] w-0.5 overflow-hidden rounded bg-surface-2">
                  {done && <span className="block h-full origin-top bg-primary animate-[fill-down_.34s_linear_both] motion-reduce:animate-none" style={{ animationDelay: i * STEP + 0.08 + "s" }} />}
                </span>
              )}
              <span
                className={cn(
                  "relative grid size-6 place-items-center rounded-full border-[1.5px] animate-pop motion-reduce:animate-none",
                  done && "border-primary bg-primary text-primary-fg",
                  now && "border-primary bg-primary-soft",
                  !done && !now && "border-border-strong bg-bg",
                )}
                style={{ animationDelay: i * STEP + "s" }}
              >
                {done && <Check className="size-3.5" aria-hidden />}
                {now && <span className="size-2 rounded-full bg-primary" />}
                {now && <span aria-hidden className="absolute -inset-[5px] rounded-full border-2 border-primary animate-[breathe_1.6s_ease-in-out_infinite_alternate] motion-reduce:animate-none" />}
              </span>
              <span className="grid pt-0.5 text-[13.5px] animate-rise motion-reduce:animate-none" style={{ animationDelay: i * STEP + 0.08 + "s" }}>
                <b className={done || now ? "font-medium text-fg" : "font-normal text-fg-muted"}>{s.title}</b>
                <span className="text-xs text-fg-subtle">{s.text}</span>
              </span>
              <time className="pt-[3px] text-xs tabular-nums text-fg-subtle">{s.time}</time>
            </li>
          );
        })}
      </ol>

      <div className="grid gap-2.5 rounded-lg bg-surface p-3.5 animate-rise [animation-delay:1500ms] motion-reduce:animate-none">
        <div className="flex items-center gap-2.5">
          <span className="grid size-8 place-items-center rounded bg-primary-soft text-primary-soft-fg"><Truck className="size-4" aria-hidden /></span>
          <div className="min-w-0 flex-1">
            <p className="text-[15px] font-medium text-fg">Ravi is on the way</p>
            <p className="flex items-center gap-1 text-xs text-fg-subtle"><MapPin className="size-3" aria-hidden />Koregaon Park · about 40 min</p>
          </div>
        </div>
        <div className="h-1.5 overflow-hidden rounded-full bg-surface-2">
          <span className="block h-full w-[70%] origin-left rounded-full bg-primary animate-grow-x [animation-delay:1650ms] motion-reduce:animate-none" />
        </div>
      </div>
    </section>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.