Commerce

Order status

A shipment tracker: delivery estimate, a track of dated steps, any delay, and the tracking number.

Built on
<ol>aria-current="step"<time>Intl.DateTimeFormatContainer queries
Client JavaScript
None
Works without React
Yes, as HTML and CSS
Source, gzipped
4.4 kB
Built from
Icon

Arrives

  1. Ordered, completed
  2. Packed, completed
  3. Shipped, current
  4. Delivered, upcoming
Try its options

Loading its options.

Code
import { OrderStatus } from "@/components/equip/order-status";

export default function Demo() {
  return (
    <OrderStatus
      size="s"
      orientation="horizontal"
      estimate="2026-11-20"
      current={2}
      steps={[
        { label: "Ordered", date: "2026-11-12" },
        { label: "Packed", date: "2026-11-13" },
        { label: "Shipped", date: "2026-11-14" },
        { label: "Delivered" },
      ]}
      style={{ width: 224 }}
    />
  );
}
Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<div style="width:224px" class="eq-order-status" data-orientation="horizontal" data-size="s">
  <div class="eq-order-status-header">
    <p class="eq-order-status-estimate">Arrives <time dateTime="2026-11-20">Friday, November 20</time></p>
  </div>
  <ol class="eq-order-status-track" aria-label="Shipment progress">
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="10" style="--_size:10px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Ordered<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-12">Nov 12</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="10" style="--_size:10px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Packed<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-13">Nov 13</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="current" aria-current="step">
      <span class="eq-order-status-line" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="10" style="--_size:10px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Shipped<span class="eq-order-status-hidden">, current</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-14">Nov 14</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="upcoming">
      <span class="eq-order-status-marker" aria-hidden="true"></span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Delivered<span class="eq-order-status-hidden">, upcoming</span></span>
      </span>
    </li>
  </ol>
</div>

Examples

Tracker with carrier

Below about 30rem of container width the track turns into a vertical list, so it fits a phone or a sidebar.

Ceramic pour-over set

Order #48213

Arrives  – 

  1. Ordered, completed
  2. Packed, completed
  3. Shipped, currentLeft the Reno warehouse
  4. Out for delivery, upcoming
  5. Delivered, upcoming

UPS GroundTracking number 1Z 999 AA1 01 2345 6784

Track package
Code
import { OrderStatus } from "@/components/equip/order-status";

export default function Example() {
  return (
    <OrderStatus
      heading="Ceramic pour-over set"
      orderNumber="#48213"
      estimate={{ from: "2026-11-19", to: "2026-11-21" }}
      current={2}
      steps={[
        { label: "Ordered", date: "2026-11-12" },
        { label: "Packed", date: "2026-11-13" },
        { label: "Shipped", date: "2026-11-14", description: "Left the Reno warehouse" },
        { label: "Out for delivery" },
        { label: "Delivered" },
      ]}
      carrier="UPS Ground"
      trackingNumber="1Z 999 AA1 01 2345 6784"
      trackingHref="https://www.ups.com/track"
    />
  );
}
HTMLworks in any framework, with the part's CSS
<div class="eq-order-status" data-orientation="auto" data-size="m">
  <div class="eq-order-status-header">
    <div class="eq-order-status-title">
      <h3 class="eq-order-status-heading">Ceramic pour-over set</h3>
      <p class="eq-order-status-order">Order <span class="eq-order-status-mono">#48213</span></p>
    </div>
    <p class="eq-order-status-estimate">Arrives <time dateTime="2026-11-19">Thursday, November 19</time><span> – </span><time dateTime="2026-11-21">Saturday, November 21</time></p>
  </div>
  <ol class="eq-order-status-track" aria-label="Shipment progress">
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Ordered<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-12">Nov 12</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Packed<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-13">Nov 13</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="current" aria-current="step">
      <span class="eq-order-status-line" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Shipped<span class="eq-order-status-hidden">, current</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-14">Nov 14</time>
        <span class="eq-order-status-description">Left the Reno warehouse</span>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="upcoming">
      <span class="eq-order-status-line" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true"></span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Out for delivery<span class="eq-order-status-hidden">, upcoming</span></span>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="upcoming">
      <span class="eq-order-status-marker" aria-hidden="true"></span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Delivered<span class="eq-order-status-hidden">, upcoming</span></span>
      </span>
    </li>
  </ol>
  <div class="eq-order-status-tracking">
    <p class="eq-order-status-carrier">
      <span class="eq-order-status-carrier-name">UPS Ground</span>
      <span><span class="eq-order-status-hidden">Tracking number </span><span class="eq-order-status-mono">1Z 999 AA1 01 2345 6784</span></span>
    </p>
    <a class="eq-order-status-link" href="https://www.ups.com/track">Track package<svg aria-hidden="true" focusable="false" class="eq-icon" data-size="14" style="--_size:14px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M19.5 13.5v5a2 2 0 0 1-2 2h-12a2 2 0 0 1-2-2v-12a2 2 0 0 1 2-2h5" /><path d="M14.5 3.5h6v6" /><path d="M20.5 3.5l-9 9" /></svg></a>
  </div>
</div>

Delayed shipment

The held step says why in words and with an icon, and assistive tech hears it as delayed rather than current.

Merino crew socks, 3 pairs

Order #48177

Arrives

  1. Ordered, completed
  2. Packed, completed
  3. Shipped, completed
  4. In transit, Delayed
  5. Delivered, upcoming

Delayed Weather in the Denver hub has held parcels for a day. Your new delivery date is Monday.

FedEx Home DeliveryTracking number 7712 6045 3381

Track package
Code
import { OrderStatus } from "@/components/equip/order-status";

export default function Example() {
  return (
    <OrderStatus
      heading="Merino crew socks, 3 pairs"
      orderNumber="#48177"
      estimate="2026-11-23"
      current={3}
      exception={{ message: "Weather in the Denver hub has held parcels for a day. Your new delivery date is Monday." }}
      steps={[
        { label: "Ordered", date: "2026-11-10" },
        { label: "Packed", date: "2026-11-11" },
        { label: "Shipped", date: "2026-11-12" },
        { label: "In transit", date: "2026-11-14" },
        { label: "Delivered" },
      ]}
      carrier="FedEx Home Delivery"
      trackingNumber="7712 6045 3381"
      trackingHref="https://www.fedex.com/fedextrack"
    />
  );
}
HTMLworks in any framework, with the part's CSS
<div class="eq-order-status" data-orientation="auto" data-size="m" data-tone="warn">
  <div class="eq-order-status-header">
    <div class="eq-order-status-title">
      <h3 class="eq-order-status-heading">Merino crew socks, 3 pairs</h3>
      <p class="eq-order-status-order">Order <span class="eq-order-status-mono">#48177</span></p>
    </div>
    <p class="eq-order-status-estimate">Arrives <time dateTime="2026-11-23">Monday, November 23</time></p>
  </div>
  <ol class="eq-order-status-track" aria-label="Shipment progress">
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Ordered<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-10">Nov 10</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Packed<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-11">Nov 11</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Shipped<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-12">Nov 12</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="current" data-tone="warn" aria-current="step">
      <span class="eq-order-status-line" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M12 4 21 19.5H3z" />
          <path d="M12 9.5v3.5M12 16.75v.01" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">In transit<span class="eq-order-status-hidden">, Delayed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-14">Nov 14</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="upcoming">
      <span class="eq-order-status-marker" aria-hidden="true"></span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Delivered<span class="eq-order-status-hidden">, upcoming</span></span>
      </span>
    </li>
  </ol>
  <div class="eq-order-status-exception" data-tone="warn">
    <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="s" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
      <path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
      <path d="M12 7v5l3.5 2.5" />
    </svg>
    <p class="eq-order-status-exception-text"><strong class="eq-order-status-exception-title">Delayed</strong> Weather in the Denver hub has held parcels for a day. Your new delivery date is Monday.</p>
  </div>
  <div class="eq-order-status-tracking">
    <p class="eq-order-status-carrier">
      <span class="eq-order-status-carrier-name">FedEx Home Delivery</span>
      <span><span class="eq-order-status-hidden">Tracking number </span><span class="eq-order-status-mono">7712 6045 3381</span></span>
    </p>
    <a class="eq-order-status-link" href="https://www.fedex.com/fedextrack">Track package<svg aria-hidden="true" focusable="false" class="eq-icon" data-size="14" style="--_size:14px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M19.5 13.5v5a2 2 0 0 1-2 2h-12a2 2 0 0 1-2-2v-12a2 2 0 0 1 2-2h5" /><path d="M14.5 3.5h6v6" /><path d="M20.5 3.5l-9 9" /></svg></a>
  </div>
</div>

Delivered, in a narrow column

Once the last step is reached the estimate gives way to the delivery date.

Walnut desk lamp

Order #47930

Delivered

  1. Ordered, completed
  2. Shipped, completed
  3. Out for delivery, completed
  4. Delivered, completedLeft with the building concierge

USPS Priority MailTracking number 9400 1118 9922 3456 7890 12

Code
import { OrderStatus } from "@/components/equip/order-status";

export default function Example() {
  return (
    <OrderStatus
      style={{ maxWidth: 320 }}
      heading="Walnut desk lamp"
      orderNumber="#47930"
      estimate="2026-11-18"
      current={3}
      steps={[
        { label: "Ordered", date: "2026-11-09" },
        { label: "Shipped", date: "2026-11-11" },
        { label: "Out for delivery", date: "2026-11-13" },
        { label: "Delivered", date: "2026-11-13", description: "Left with the building concierge" },
      ]}
      carrier="USPS Priority Mail"
      trackingNumber="9400 1118 9922 3456 7890 12"
    />
  );
}
HTMLworks in any framework, with the part's CSS
<div style="max-width:320px" class="eq-order-status" data-orientation="auto" data-size="m">
  <div class="eq-order-status-header">
    <div class="eq-order-status-title">
      <h3 class="eq-order-status-heading">Walnut desk lamp</h3>
      <p class="eq-order-status-order">Order <span class="eq-order-status-mono">#47930</span></p>
    </div>
    <p class="eq-order-status-estimate" data-delivered="true">Delivered <time dateTime="2026-11-13">Friday, November 13</time></p>
  </div>
  <ol class="eq-order-status-track" aria-label="Shipment progress">
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Ordered<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-09">Nov 9</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Shipped<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-11">Nov 11</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Out for delivery<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-13">Nov 13</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="completed" aria-current="step">
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Delivered<span class="eq-order-status-hidden">, completed</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-13">Nov 13</time>
        <span class="eq-order-status-description">Left with the building concierge</span>
      </span>
    </li>
  </ol>
  <div class="eq-order-status-tracking">
    <p class="eq-order-status-carrier">
      <span class="eq-order-status-carrier-name">USPS Priority Mail</span>
      <span><span class="eq-order-status-hidden">Tracking number </span><span class="eq-order-status-mono">9400 1118 9922 3456 7890 12</span></span>
    </p>
  </div>
</div>

French

Dates come from Intl in the given locale; labels replaces the built-in words.

Sac à dos en toile cirée

Commande FR-20931

Livraison prévue  –  novembre

  1. Commandé, terminé
  2. Préparé, en cours
  3. Expédié, à venir
  4. Livré, à venir

ColissimoNuméro de suivi 6A12345678901

Suivre le colis
Code
import { OrderStatus } from "@/components/equip/order-status";

export default function Example() {
  return (
    <OrderStatus
      locale="fr-FR"
      heading="Sac à dos en toile cirée"
      orderNumber="FR-20931"
      estimate={{ from: "2026-11-18", to: "2026-11-19" }}
      current={1}
      steps={[
        { label: "Commandé", date: "2026-11-13" },
        { label: "Préparé", date: "2026-11-14" },
        { label: "Expédié" },
        { label: "Livré" },
      ]}
      carrier="Colissimo"
      trackingNumber="6A12345678901"
      trackingHref="https://www.laposte.fr/outils/suivre-vos-envois"
      labels={{
        progress: "Suivi de l'envoi",
        arrives: "Livraison prévue",
        delivered: "Livré",
        order: "Commande",
        tracking: "Numéro de suivi",
        track: "Suivre le colis",
        completed: "terminé",
        current: "en cours",
        upcoming: "à venir",
        delayed: "Retardé",
        problem: "Problème de livraison",
      }}
    />
  );
}
HTMLworks in any framework, with the part's CSS
<div class="eq-order-status" data-orientation="auto" data-size="m">
  <div class="eq-order-status-header">
    <div class="eq-order-status-title">
      <h3 class="eq-order-status-heading">Sac à dos en toile cirée</h3>
      <p class="eq-order-status-order">Commande <span class="eq-order-status-mono">FR-20931</span></p>
    </div>
    <p class="eq-order-status-estimate">Livraison prévue <time dateTime="2026-11-18">mercredi 18</time><span> – </span><time dateTime="2026-11-19">jeudi 19</time><span> novembre</span></p>
  </div>
  <ol class="eq-order-status-track" aria-label="Suivi de l&#x27;envoi">
    <li class="eq-order-status-step" data-state="completed">
      <span class="eq-order-status-line" data-filled="true" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Commandé<span class="eq-order-status-hidden">, terminé</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-13">13 nov.</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="current" aria-current="step">
      <span class="eq-order-status-line" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true">
        <svg aria-hidden="true" focusable="false" class="eq-icon" data-size="12" style="--_size:12px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
          <path d="M4.5 12.5l5 5 10-11" />
        </svg>
      </span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Préparé<span class="eq-order-status-hidden">, en cours</span></span>
        <time class="eq-order-status-date" dateTime="2026-11-14">14 nov.</time>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="upcoming">
      <span class="eq-order-status-line" aria-hidden="true"></span>
      <span class="eq-order-status-marker" aria-hidden="true"></span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Expédié<span class="eq-order-status-hidden">, à venir</span></span>
      </span>
    </li>
    <li class="eq-order-status-step" data-state="upcoming">
      <span class="eq-order-status-marker" aria-hidden="true"></span>
      <span class="eq-order-status-text">
        <span class="eq-order-status-label">Livré<span class="eq-order-status-hidden">, à venir</span></span>
      </span>
    </li>
  </ol>
  <div class="eq-order-status-tracking">
    <p class="eq-order-status-carrier">
      <span class="eq-order-status-carrier-name">Colissimo</span>
      <span><span class="eq-order-status-hidden">Numéro de suivi </span><span class="eq-order-status-mono">6A12345678901</span></span>
    </p>
    <a class="eq-order-status-link" href="https://www.laposte.fr/outils/suivre-vos-envois">Suivre le colis<svg aria-hidden="true" focusable="false" class="eq-icon" data-size="14" style="--_size:14px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M19.5 13.5v5a2 2 0 0 1-2 2h-12a2 2 0 0 1-2-2v-12a2 2 0 0 1 2-2h5" /><path d="M14.5 3.5h6v6" /><path d="M20.5 3.5l-9 9" /></svg></a>
  </div>
</div>

Props

PropTypeDefaultWhat it does
steps{ label: ReactNode; date?: string; description?: ReactNode }[]noneThe stages in order. date is an ISO date.
currentnumbernoneIndex of the latest step reached. Earlier steps are completed.
estimatestring | { from: string; to?: string }noneExpected delivery as an ISO date or a range. Replaced by the delivery date once the last step is reached.
carrierReactNodenoneCarrier and service, such as UPS Ground.
trackingNumberstringnoneShown in monospace.
trackingHrefstringnoneThe carrier's tracking page; adds a Track package link.
exception{ message: ReactNode; tone?: "warn" | "danger"; title?: ReactNode }noneMarks the current step as held up and shows the reason. warn reads Delayed, danger Delivery problem.
orderNumberstringnoneShown beside the heading.
headingReactNodenoneUsually the product or order name.
headingLevel2 | 3 | 4 | 5 | 63Level of the heading element.
localestring"en-US"Formats every date, in UTC.
orientation"auto" | "horizontal" | "vertical""auto"auto is a vertical list until the container is 30rem wide.
size"s" | "m""m"s has smaller markers and text, for cards and summaries.
labelsPartial<OrderStatusLabels>noneReplaces the built-in words, such as Arrives and completed, for another language.
...restComponentProps<"div">noneEvery native div attribute, applied to the root.

Anatomy

root.eq-order-statusThe container the layout queries.
header.eq-order-status-header
title.eq-order-status-titleHolds the heading and order number.
heading.eq-order-status-heading
order.eq-order-status-order
estimate.eq-order-status-estimateArrives or Delivered, with the date.
mono.eq-order-status-monoOrder and tracking numbers.
track.eq-order-status-trackThe ordered list of steps.
step.eq-order-status-stepdata-state is completed, current or upcoming; data-tone marks a held step.
line.eq-order-status-lineJoins a step to the next and fills once passed.
marker.eq-order-status-marker
text.eq-order-status-text
label.eq-order-status-label
date.eq-order-status-date
description.eq-order-status-description
exception.eq-order-status-exception
exception text.eq-order-status-exception-text
exception title.eq-order-status-exception-title
tracking.eq-order-status-tracking
carrier.eq-order-status-carrier
carrier name.eq-order-status-carrier-name
link.eq-order-status-link
hidden.eq-order-status-hiddenText for assistive tech only, such as each step's state.

Notes

  • A status display, not navigation: steps are plain list items and the only link is the carrier's tracking page.
  • Each step's state is spoken after its label, so the track reads as Shipped, current rather than by colour.
  • A held step says Delayed or Delivery problem in words and with an icon as well as its tone.
  • Dates are formatted in UTC, so the server and the browser print the same day.
  • Renders on the server with no client JavaScript.

Source

Two files, and they are the whole part. Open either to read it, copy it straight from its bar, or add the part to your loadout and install it with everything else you picked.

components/equip/order-status.tsx245 lines
import type { ComponentProps, ReactNode } from "react";
import { Icon } from "./icon";
import "./order-status.css";

export type OrderStatusStep = {
  label: ReactNode;
  /** ISO date the step was reached, or is expected. */
  date?: string;
  description?: ReactNode;
};

export type OrderStatusLabels = {
  /** Names the list of steps. */
  progress: string;
  arrives: string;
  delivered: string;
  order: string;
  tracking: string;
  track: string;
  completed: string;
  current: string;
  upcoming: string;
  delayed: string;
  problem: string;
};

const DEFAULT_LABELS: OrderStatusLabels = {
  progress: "Shipment progress",
  arrives: "Arrives",
  delivered: "Delivered",
  order: "Order",
  tracking: "Tracking number",
  track: "Track package",
  completed: "completed",
  current: "current",
  upcoming: "upcoming",
  delayed: "Delayed",
  problem: "Delivery problem",
};

export type OrderStatusProps = Omit<ComponentProps<"div">, "children"> & {
  steps: OrderStatusStep[];
  /** Index of the latest step reached. Steps before it are completed. */
  current: number;
  /** Expected delivery: one ISO date, or a range. Replaced by the delivery date once the last step is reached. */
  estimate?: string | { from: string; to?: string };
  carrier?: ReactNode;
  trackingNumber?: string;
  /** The carrier's tracking page. */
  trackingHref?: string;
  /** Marks the current step as held up, with the reason in words. */
  exception?: { message: ReactNode; tone?: "warn" | "danger"; title?: ReactNode };
  orderNumber?: string;
  heading?: ReactNode;
  headingLevel?: 2 | 3 | 4 | 5 | 6;
  /** Formats every date. */
  locale?: string;
  /** Horizontal track, vertical list, or horizontal only when the container is wide enough. */
  orientation?: "auto" | "horizontal" | "vertical";
  size?: "s" | "m";
  /** Replaces the built-in words, for another language. */
  labels?: Partial<OrderStatusLabels>;
};

/** A bare date is read as midnight UTC, and every date is shown in UTC, so server and browser agree. */
function toDate(iso: string) {
  return new Date(/^\d{4}-\d{2}-\d{2}$/.test(iso) ? `${iso}T00:00:00Z` : iso);
}

export function OrderStatus({
  steps,
  current,
  estimate,
  carrier,
  trackingNumber,
  trackingHref,
  exception,
  orderNumber,
  heading,
  headingLevel = 3,
  locale = "en-US",
  orientation = "auto",
  size = "m",
  labels: labelOverrides,
  className,
  ...rest
}: OrderStatusProps) {
  const labels = { ...DEFAULT_LABELS, ...labelOverrides };
  const last = steps.length - 1;
  const at = Math.max(0, Math.min(current, last));
  const delivered = at === last && !exception;
  const tone = exception ? (exception.tone ?? "warn") : undefined;

  const short = new Intl.DateTimeFormat(locale, { month: "short", day: "numeric", timeZone: "UTC" });
  const long = new Intl.DateTimeFormat(locale, { weekday: "long", month: "long", day: "numeric", timeZone: "UTC" });

  let headline: ReactNode = null;
  const deliveredOn = steps[last]?.date;
  if (delivered) {
    headline = (
      <>
        {labels.delivered}
        {deliveredOn && (
          <>
            {" "}
            <time dateTime={deliveredOn}>{long.format(toDate(deliveredOn))}</time>
          </>
        )}
      </>
    );
  } else if (estimate) {
    const from = typeof estimate === "string" ? estimate : estimate.from;
    const to = typeof estimate === "string" ? undefined : estimate.to;
    headline = (
      <>
        {labels.arrives}{" "}
        {to ? (
          // Each end of the range in its own time element; shared words such as the month sit between.
          long.formatRangeToParts(toDate(from), toDate(to)).reduce<{ source: string; text: string }[]>((runs, part) => {
            const prev = runs[runs.length - 1];
            if (prev && prev.source === part.source) prev.text += part.value;
            else runs.push({ source: part.source, text: part.value });
            return runs;
          }, []).map((run, i) =>
            run.source === "shared" ? (
              <span key={i}>{run.text}</span>
            ) : (
              <time key={i} dateTime={run.source === "startRange" ? from : to}>
                {run.text}
              </time>
            ),
          )
        ) : (
          <time dateTime={from}>{long.format(toDate(from))}</time>
        )}
      </>
    );
  }

  const glyph = size === "s" ? 10 : 12;
  const Heading = `h${headingLevel}` as const;
  const hasHeader = heading != null || orderNumber != null || headline != null;
  const hasTracking = carrier != null || trackingNumber != null || trackingHref != null;

  return (
    <div
      {...rest}
      className={["eq-order-status", className].filter(Boolean).join(" ")}
      data-orientation={orientation}
      data-size={size}
      data-tone={tone}
    >
      {hasHeader && (
        <div className="eq-order-status-header">
          {(heading != null || orderNumber != null) && (
            <div className="eq-order-status-title">
              {heading != null && <Heading className="eq-order-status-heading">{heading}</Heading>}
              {orderNumber != null && (
                <p className="eq-order-status-order">
                  {labels.order} <span className="eq-order-status-mono">{orderNumber}</span>
                </p>
              )}
            </div>
          )}
          {headline != null && (
            <p className="eq-order-status-estimate" data-delivered={delivered || undefined}>
              {headline}
            </p>
          )}
        </div>
      )}

      <ol className="eq-order-status-track" aria-label={labels.progress}>
        {steps.map((step, index) => {
          const state = index < at || (index === at && delivered) ? "completed" : index === at ? "current" : "upcoming";
          const held = index === at && tone !== undefined;
          return (
            <li
              key={index}
              className="eq-order-status-step"
              data-state={state}
              data-tone={held ? tone : undefined}
              aria-current={index === at ? "step" : undefined}
            >
              {index < last && <span className="eq-order-status-line" data-filled={index < at || undefined} aria-hidden="true" />}
              <span className="eq-order-status-marker" aria-hidden="true">
                {held ? (
                  <Icon name="warning" size={glyph} />
                ) : state === "completed" || state === "current" ? (
                  <Icon name="check" size={glyph} />
                ) : null}
              </span>
              <span className="eq-order-status-text">
                <span className="eq-order-status-label">
                  {step.label}
                  <span className="eq-order-status-hidden">
                    , {held ? (tone === "danger" ? labels.problem : labels.delayed) : labels[state]}
                  </span>
                </span>
                {step.date && (
                  <time className="eq-order-status-date" dateTime={step.date}>
                    {short.format(toDate(step.date))}
                  </time>
                )}
                {step.description != null && <span className="eq-order-status-description">{step.description}</span>}
              </span>
            </li>
          );
        })}
      </ol>

      {exception && (
        <div className="eq-order-status-exception" data-tone={tone}>
          <Icon name={tone === "danger" ? "warning" : "clock"} size="s" />
          <p className="eq-order-status-exception-text">
            <strong className="eq-order-status-exception-title">
              {exception.title ?? (tone === "danger" ? labels.problem : labels.delayed)}
            </strong>{" "}
            {exception.message}
          </p>
        </div>
      )}

      {hasTracking && (
        <div className="eq-order-status-tracking">
          <p className="eq-order-status-carrier">
            {carrier != null && <span className="eq-order-status-carrier-name">{carrier}</span>}
            {trackingNumber != null && (
              <span>
                <span className="eq-order-status-hidden">{labels.tracking} </span>
                <span className="eq-order-status-mono">{trackingNumber}</span>
              </span>
            )}
          </p>
          {trackingHref && (
            <a className="eq-order-status-link" href={trackingHref}>
              {labels.track}
              <Icon name="external" size={14} />
            </a>
          )}
        </div>
      )}
    </div>
  );
}
components/equip/order-status.css371 lines
@layer equip {
  .eq-order-status {
    --_m: 20px;
    --_tone: var(--eq-warn);

    container-type: inline-size;
    box-sizing: border-box;
    inline-size: 100%;
    display: grid;
    gap: var(--eq-space-4);
    min-width: 0;
    color: var(--eq-ink);
    font: var(--eq-weight) var(--eq-text-s) / 1.4 var(--eq-font);
  }

  .eq-order-status[data-size="s"] {
    --_m: 16px;
    gap: var(--eq-space-3);
  }

  .eq-order-status[data-tone="danger"],
  .eq-order-status-step[data-tone="danger"],
  .eq-order-status-exception[data-tone="danger"] {
    --_tone: var(--eq-danger);
  }

  /* ---- header ---- */

  .eq-order-status-header {
    display: grid;
    gap: var(--eq-space-1);
  }

  .eq-order-status-title {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--eq-space-1) var(--eq-space-3);
  }

  .eq-order-status-heading {
    margin: 0;
    font: var(--eq-weight-strong) var(--eq-text-m) / 1.3 var(--eq-font);
  }

  .eq-order-status-order {
    margin: 0;
    color: var(--eq-ink-2);
  }

  .eq-order-status-estimate {
    margin: 0;
    font: var(--eq-weight-strong) var(--eq-text-l) / 1.3 var(--eq-font);
    letter-spacing: -0.01em;
    text-wrap: balance;
  }

  .eq-order-status[data-size="s"] .eq-order-status-estimate {
    font-size: var(--eq-text-m);
    letter-spacing: 0;
  }

  .eq-order-status-mono {
    font-family: var(--eq-font-mono);
    font-variation-settings: var(--eq-font-mono-settings);
    font-size: 0.95em;
    overflow-wrap: anywhere;
  }

  /* ---- track: a vertical list by default ---- */

  .eq-order-status-track {
    display: grid;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .eq-order-status-step {
    position: relative;
    display: grid;
    grid-template-columns: var(--_m) minmax(0, 1fr);
    column-gap: var(--eq-space-3);
    min-width: 0;
    padding-bottom: var(--eq-space-4);
  }

  .eq-order-status-step:last-child {
    padding-bottom: 0;
  }

  .eq-order-status-line {
    position: absolute;
    left: calc(var(--_m) / 2 - 1px);
    top: calc(var(--_m) + var(--eq-space-1));
    bottom: var(--eq-space-1);
    width: 2px;
    border-radius: var(--eq-radius-full);
    background: var(--eq-line-strong);
    overflow: hidden;
  }

  .eq-order-status-line::after {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--eq-solid);
    scale: 1 0;
    transform-origin: center top;
    transition: scale var(--eq-dur-move) var(--eq-spring);
  }

  .eq-order-status-line[data-filled]::after {
    scale: 1;
  }

  .eq-order-status-marker {
    box-sizing: border-box;
    display: grid;
    place-items: center;
    width: var(--_m);
    height: var(--_m);
    border: 1px solid var(--eq-line-strong);
    border-radius: var(--eq-radius-full);
    background: var(--eq-raised);
    color: var(--eq-solid-ink);
    transition:
      background-color var(--eq-dur) var(--eq-ease),
      border-color var(--eq-dur) var(--eq-ease),
      box-shadow var(--eq-dur) var(--eq-ease);
  }

  .eq-order-status-step:is([data-state="completed"], [data-state="current"]) .eq-order-status-marker {
    border-color: var(--eq-solid);
    background: var(--eq-solid);
  }

  /* The latest step reached wears a halo, so it reads as where the parcel is now. */
  .eq-order-status-step[data-state="current"] .eq-order-status-marker {
    box-shadow: 0 0 0 3px var(--eq-tint-strong);
  }

  .eq-order-status-step[data-tone] .eq-order-status-marker {
    border-color: var(--_tone);
    background: var(--_tone);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--_tone) 22%, transparent);
  }

  .eq-order-status-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    /* Sets the label's first line on the marker's centre. */
    padding-top: calc((var(--_m) - 1.4em) / 2);
  }

  .eq-order-status-label {
    font-weight: var(--eq-weight-strong);
    overflow-wrap: break-word;
  }

  .eq-order-status-step[data-state="upcoming"] .eq-order-status-label {
    color: var(--eq-ink-2);
    font-weight: var(--eq-weight);
  }

  .eq-order-status-step[data-tone] .eq-order-status-label {
    color: var(--_tone);
  }

  .eq-order-status-date {
    color: var(--eq-ink-2);
    font-size: var(--eq-text-xs);
    font-variant-numeric: tabular-nums;
  }

  .eq-order-status-description {
    color: var(--eq-ink-2);
  }

  /* ---- track: horizontal, when asked or when the container is wide ---- */

  .eq-order-status[data-orientation="horizontal"] .eq-order-status-track {
    display: flex;
  }

  .eq-order-status[data-orientation="horizontal"] .eq-order-status-step {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    gap: var(--eq-space-2);
    padding: 0 var(--eq-space-2) 0 0;
  }

  .eq-order-status[data-orientation="horizontal"] .eq-order-status-step:last-child {
    padding-inline-end: 0;
  }

  .eq-order-status[data-orientation="horizontal"] .eq-order-status-line {
    top: calc(var(--_m) / 2 - 1px);
    bottom: auto;
    left: calc(var(--_m) + var(--eq-space-1));
    right: var(--eq-space-1);
    width: auto;
    height: 2px;
  }

  .eq-order-status[data-orientation="horizontal"] .eq-order-status-line::after {
    scale: 0 1;
    transform-origin: left center;
  }

  .eq-order-status[data-orientation="horizontal"] .eq-order-status-line[data-filled]::after {
    scale: 1;
  }

  .eq-order-status[data-orientation="horizontal"] .eq-order-status-text {
    padding-top: 0;
  }

  .eq-order-status[data-orientation="horizontal"][data-size="s"] .eq-order-status-step {
    padding-inline-end: var(--eq-space-1);
  }

  .eq-order-status[data-orientation="horizontal"][data-size="s"] .eq-order-status-step:last-child {
    padding-inline-end: 0;
  }

  .eq-order-status[data-orientation="horizontal"][data-size="s"] .eq-order-status-label {
    font-size: var(--eq-text-xs);
  }

  @container (width >= 30rem) {
    .eq-order-status[data-orientation="auto"] .eq-order-status-track {
      display: flex;
    }

    .eq-order-status[data-orientation="auto"] .eq-order-status-step {
      flex: 1 1 0;
      display: flex;
      flex-direction: column;
      gap: var(--eq-space-2);
      padding: 0 var(--eq-space-3) 0 0;
    }

    .eq-order-status[data-orientation="auto"] .eq-order-status-step:last-child {
      padding-inline-end: 0;
    }

    .eq-order-status[data-orientation="auto"] .eq-order-status-line {
      top: calc(var(--_m) / 2 - 1px);
      bottom: auto;
      left: calc(var(--_m) + var(--eq-space-1));
      right: var(--eq-space-1);
      width: auto;
      height: 2px;
    }

    .eq-order-status[data-orientation="auto"] .eq-order-status-line::after {
      scale: 0 1;
      transform-origin: left center;
    }

    .eq-order-status[data-orientation="auto"] .eq-order-status-line[data-filled]::after {
      scale: 1;
    }

    .eq-order-status[data-orientation="auto"] .eq-order-status-text {
      padding-top: 0;
    }
  }

  /* ---- exception ---- */

  .eq-order-status-exception {
    display: flex;
    align-items: flex-start;
    gap: var(--eq-space-2);
    padding: var(--eq-space-2) var(--eq-space-3);
    border: 1px solid color-mix(in srgb, var(--_tone) 35%, transparent);
    border-radius: var(--eq-radius-m);
    background: color-mix(in srgb, var(--_tone) 8%, transparent);
  }

  .eq-order-status-exception > svg {
    margin-top: 2px;
    color: var(--_tone);
  }

  .eq-order-status-exception-text {
    margin: 0;
    min-width: 0;
  }

  .eq-order-status-exception-title {
    color: var(--_tone);
    font-weight: var(--eq-weight-strong);
  }

  /* ---- carrier and tracking ---- */

  .eq-order-status-tracking {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--eq-space-2) var(--eq-space-4);
    padding-top: var(--eq-space-3);
    border-top: 1px solid var(--eq-line);
  }

  .eq-order-status-carrier {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--eq-space-1) var(--eq-space-2);
    margin: 0;
    min-width: 0;
    color: var(--eq-ink-2);
  }

  .eq-order-status-carrier-name {
    color: var(--eq-ink);
    font-weight: var(--eq-weight-strong);
  }

  .eq-order-status-link {
    display: inline-flex;
    align-items: center;
    gap: var(--eq-space-1);
    min-height: 24px;
    border-radius: var(--eq-radius-s);
    color: var(--eq-ink);
    font-weight: var(--eq-weight-strong);
    text-decoration: underline;
    text-decoration-color: var(--eq-line-strong);
    text-underline-offset: 3px;
    transition:
      text-decoration-color var(--eq-dur) var(--eq-ease),
      scale var(--eq-dur) var(--eq-ease);
  }

  @media (hover: hover) {
    .eq-order-status-link:hover {
      text-decoration-color: currentColor;
    }
  }

  .eq-order-status-link:focus-visible {
    outline: 2px solid var(--eq-focus);
    outline-offset: 2px;
  }

  .eq-order-status-link:active {
    scale: var(--eq-press);
  }

  .eq-order-status-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}

EqUIp is in beta: free for non-commercial use, no warranty, and things can change. Terms

Nothing picked yet. Press A on a part, or use the plus on the board, and the install command builds here.

Loading the index.

Loading the index