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.
npx shadcn@latest add https://webberui.com/r/parcel-journey.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<ParcelJourney />
Installation
npx shadcn@latest add https://webberui.com/r/parcel-journey.jsonOr, 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
| Prop | Type | Default | Description |
|---|---|---|---|
steps | ParcelStep[] | — | The stops along the journey, ordered from origin to destination |
currentStep | number | — | Index of the current stop (0-based); clamped automatically to the valid range |
delivered | boolean | false | When true, the current node is shown with a check mark instead and the parcel stops pulsing |
estimatedDelivery | Date | string | — | Estimated delivery date; when provided, it is presented at the top with a calendar page-flip animation |
deliveryLabel | string | "Estimated delivery" | Label text describing the delivery date |
locale | string | "zh-TW" | Locale used for date formatting |
className | string | — | Class appended to the outermost container |
ParcelStep
| Field | Type | Description |
|---|---|---|
id | string | Unique node key; the index is used as the key when not provided |
label | string | Node title (such as "Shipped" or "In transit") |
description | string | Supplementary detail (location, tracking number, carrier, and so on) |
timestamp | string | Timestamp 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 whencurrentStepchanges 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
rotateXpage flip.
Accessibility
- The component is presented as a semantic
<ol>/<li>, with the current stop markedaria-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 isaria-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.
Versus Tray
Check off products and a comparison tray slides out from the bottom; the thumbnails FLIP into a full-width spec table that scans row by row and marks the winner of each one.
Stock Pulse
A stock pulse meter — a falling liquid level shows consumption, crossing a threshold changes color and shakes, and once it enters low stock the whole bar breathes while the remaining count rolls down.