WebberUI

Parcel Journey

A post-purchase tracking timeline — the parcel advances along a vertical dashed path as the order status changes, completed nodes check off and light up in order, the current node pulses like a breath, and the estimated delivery date is presented as a flipping calendar page.

Loading preview…
npx shadcn@latest add https://webberui.com/r/parcel-journey.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

3
<ParcelJourney />

Installation

npx shadcn@latest add https://webberui.com/r/parcel-journey.json

Or, once registries are configured in components.json, install it as @webberui/parcel-journey.

Usage

import { ParcelJourney, type ParcelStep } from "@/components/ui/parcel-journey";

const steps: ParcelStep[] = [
  { label: "Order placed", description: "We have received your order.", timestamp: "7/10 09:12" },
  { label: "Shipped", description: "Taipei transit center", timestamp: "7/11 18:40" },
  { label: "In transit", description: "Hsinchu delivery station", timestamp: "7/13 08:05" },
  { label: "Out for delivery" },
  { label: "Delivered" },
];

<ParcelJourney
  steps={steps}
  currentStep={2}
  estimatedDelivery="2026-07-13"
/>;

Nodes before currentStep count as complete (checked off), and that node is in progress (showing the parcel with a breathing pulse). When the parcel reaches its destination, pass delivered so the current node finishes with a check mark instead:

<ParcelJourney steps={steps} currentStep={4} delivered estimatedDelivery={new Date()} />

Props

PropTypeDefaultDescription
stepsParcelStep[]The stops along the journey, ordered from origin to destination
currentStepnumberIndex of the current stop (0-based); clamped automatically to the valid range
deliveredbooleanfalseWhen true, the current node is shown with a check mark instead and the parcel stops pulsing
estimatedDeliveryDate | stringEstimated delivery date; when provided, it is presented at the top with a calendar page-flip animation
deliveryLabelstring"Estimated delivery"Label text describing the delivery date
localestring"zh-TW"Locale used for date formatting
classNamestringClass appended to the outermost container

ParcelStep

FieldTypeDescription
idstringUnique node key; the index is used as the key when not provided
labelstringNode title (such as "Shipped" or "In transit")
descriptionstringSupplementary detail (location, tracking number, carrier, and so on)
timestampstringTimestamp text, shown to the right of the title

How it works

  • Nodes are laid out at a uniform diameter, and the vertical dashed path connects the centers of adjacent nodes; the portion already travelled fills as a solid line from top to bottom, with the leading edge of the fill landing exactly at the node where the parcel is.
  • The parcel is implemented with a shared layoutId, so when currentStep changes it glides smoothly along the path to the new node rather than jumping instantly.
  • Completed nodes check off in order (staggered) the first time they are revealed, and the parcel at the current node keeps breathing while a ring spreads outward and fades.
  • When the estimated delivery date changes, the calendar card at the top reveals the new date with a rotateX page flip.

Accessibility

  • The component is presented as a semantic <ol> / <li>, with the current stop marked aria-current="step".
  • A role="status" at the start provides a plain-text status summary (the estimated delivery date and the current stop), and every animated node and icon is aria-hidden.
  • When the user has "reduce motion" enabled at the system level, the parcel glide, the pulse, the fill advance, and the calendar page flip are all turned off and the state switches straight into place.

On this page