Open demo

CSS code

HTML
<div class="trk">
  <div class="hd">
    <div><h2>Parcel HB-4471-902</h2><p>Two items · 3.4 kg · Signed for</p></div>
    <span class="eta">Arrives Thu</span>
  </div>
  <ol class="steps">
    <li class="st done"><b><span class="sr">Completed: </span>Order received</b><span class="when"><time datetime="2024-09-02T09:14">Mon 2 Sep, 09:14</time></span></li>
    <li class="st done"><b><span class="sr">Completed: </span>Packed at Reading depot</b><span class="when"><time datetime="2024-09-02T16:40">Mon 2 Sep, 16:40</time></span></li>
    <li class="st bad"><b><span class="sr">Problem: </span>Delivery attempted</b><span class="when"><time datetime="2024-09-04T11:02">Wed 4 Sep, 11:02</time></span>
      <p>Nobody available to sign. The driver left a card and will retry automatically.</p></li>
    <li class="st now" aria-current="step"><b><span class="sr">Current step: </span>Out for delivery again</b><span class="when"><time datetime="2024-09-05T07:20">Thu 5 Sep, 07:20</time></span>
      <p>On vehicle 41, currently 6 stops away.</p></li>
    <li class="st todo"><b><span class="sr">Upcoming: </span>Delivered</b><span class="when">Expected before <time datetime="2024-09-05T18:00">18:00</time></span></li>
  </ol>
</div>
CSS
/* Delivery tracking card: an <ol> of events. Each step's state is written out in visually hidden text
   (and aria-current on the live step); the coloured dots and spine repeat it visually. */
.trk{--tk-done:#16a34a;--tk-now:#4f46e5;--tk-bad:#dc2626;--tk-off:#e3e8f2;--tk-muted:#5f6a86;--tk-light:#646f8b;
  width:100%;max-width:520px;background:#fff;border:1px solid var(--tk-off);border-radius:15px;padding:24px 26px 26px}
.trk,.trk *{box-sizing:border-box}
.trk :where(h2,p,ol,li){margin:0;padding:0;list-style:none}
.trk .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.trk .hd{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:22px;gap:14px}
.trk .hd h2{font-size:1rem;letter-spacing:-.01em}
.trk .hd p{font-size:.79rem;color:var(--tk-muted);margin-top:3px}
.trk .eta{font-size:.74rem;font-weight:800;color:#15803d;background:#e8f8ee;padding:5px 11px;border-radius:99px;white-space:nowrap}
.trk .steps{position:relative;padding-left:30px}
.trk .st{position:relative;padding-bottom:24px}
.trk .st:last-child{padding-bottom:0}
.trk .st::before{content:"";position:absolute;left:-24px;top:16px;bottom:-8px;width:2px;background:var(--tk-off)}
.trk .st:last-child::before{display:none}
.trk .st.done::before{background:var(--tk-done)}
.trk .st::after{content:"";position:absolute;left:-30px;top:3px;width:14px;height:14px;border-radius:50%;
  background:#fff;border:3px solid var(--tk-off)}
.trk .st.done::after{background:var(--tk-done);border-color:var(--tk-done)}
.trk .st.now::after{border-color:var(--tk-now);animation:trk-pulse 2s ease-out infinite}
.trk .st.bad::after{background:var(--tk-bad);border-color:var(--tk-bad)}
@keyframes trk-pulse{0%{box-shadow:0 0 0 0 rgba(79,70,229,.45)}70%{box-shadow:0 0 0 9px rgba(79,70,229,0)}100%{box-shadow:0 0 0 0 rgba(79,70,229,0)}}
.trk .st b{font-size:.9rem;display:block}
.trk .st .when{font-size:.76rem;color:var(--tk-light);display:block;margin-top:2px;font-variant-numeric:tabular-nums}
.trk .st p{font-size:.81rem;color:var(--tk-muted);margin-top:6px;line-height:1.6}
.trk .st.todo b{color:var(--tk-light)}
.trk .st.bad b{color:#b91c1c}
.trk .st.bad p{background:#fdecec;border-radius:9px;padding:9px 12px;color:#991b1b}
@media(prefers-reduced-motion:reduce){.trk .st.now::after{animation:none;box-shadow:0 0 0 5px rgba(79,70,229,.22)}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="trk card w-100"><div class="card-body">
  <div class="hd d-flex justify-content-between align-items-start">
    <div><h2>Parcel HB-4471-902</h2><p>Two items · 3.4 kg · Signed for</p></div>
    <span class="eta badge rounded-pill">Arrives Thu</span>
  </div>
  <ol class="steps list-unstyled position-relative m-0">
    <li class="st done position-relative"><b class="d-block"><span class="visually-hidden">Completed: </span>Order received</b><span class="when d-block"><time datetime="2024-09-02T09:14">Mon 2 Sep, 09:14</time></span></li>
    <li class="st done position-relative"><b class="d-block"><span class="visually-hidden">Completed: </span>Packed at Reading depot</b><span class="when d-block"><time datetime="2024-09-02T16:40">Mon 2 Sep, 16:40</time></span></li>
    <li class="st bad position-relative"><b class="d-block"><span class="visually-hidden">Problem: </span>Delivery attempted</b><span class="when d-block"><time datetime="2024-09-04T11:02">Wed 4 Sep, 11:02</time></span>
      <p>Nobody available to sign. The driver left a card and will retry automatically.</p></li>
    <li class="st now position-relative" aria-current="step"><b class="d-block"><span class="visually-hidden">Current step: </span>Out for delivery again</b><span class="when d-block"><time datetime="2024-09-05T07:20">Thu 5 Sep, 07:20</time></span>
      <p>On vehicle 41, currently 6 stops away.</p></li>
    <li class="st todo position-relative"><b class="d-block"><span class="visually-hidden">Upcoming: </span>Delivered</b><span class="when d-block">Expected before <time datetime="2024-09-05T18:00">18:00</time></span></li>
  </ol>
</div></div>
CSS
/* Bootstrap .card and .badge; the spine, dots and pulse are custom CSS. State is in .visually-hidden text
   (and aria-current on the live step); the coloured dots repeat it visually. */
.trk{--tk-done:#16a34a;--tk-now:#4f46e5;--tk-bad:#dc2626;--tk-off:#e3e8f2;--tk-muted:#5f6a86;--tk-light:#646f8b;
  --bs-card-border-color:var(--tk-off);--bs-card-border-radius:15px;--bs-card-color:#17203a;max-width:520px}
.trk .card-body{padding:24px 26px 26px}
.trk .hd{margin-bottom:22px;gap:14px}
.trk .hd h2{font-size:1rem;font-weight:700;letter-spacing:-.01em;line-height:normal;margin:0}
.trk .hd p{font-size:.79rem;color:var(--tk-muted);margin:3px 0 0}
.trk .eta{--bs-badge-font-size:.74rem;--bs-badge-font-weight:800;--bs-badge-padding-y:5px;--bs-badge-padding-x:11px;color:#15803d;background:#e8f8ee;line-height:normal}
.trk .steps{padding-left:30px}
.trk .st{padding-bottom:24px}
.trk .st:last-child{padding-bottom:0}
.trk .st::before{content:"";position:absolute;left:-24px;top:16px;bottom:-8px;width:2px;background:var(--tk-off)}
.trk .st:last-child::before{display:none}
.trk .st.done::before{background:var(--tk-done)}
.trk .st::after{content:"";position:absolute;left:-30px;top:3px;width:20px;height:20px;border-radius:50%;
  background:#fff;border:3px solid var(--tk-off)}
.trk .st.done::after{background:var(--tk-done);border-color:var(--tk-done)}
.trk .st.now::after{border-color:var(--tk-now);animation:trk-pulse 2s ease-out infinite}
.trk .st.bad::after{background:var(--tk-bad);border-color:var(--tk-bad)}
@keyframes trk-pulse{0%{box-shadow:0 0 0 0 rgba(79,70,229,.45)}70%{box-shadow:0 0 0 9px rgba(79,70,229,0)}100%{box-shadow:0 0 0 0 rgba(79,70,229,0)}}
.trk .st b{font-size:.9rem}
.trk .st .when{font-size:.76rem;color:var(--tk-light);margin-top:2px;font-variant-numeric:tabular-nums}
.trk .st p{font-size:.81rem;color:var(--tk-muted);margin:6px 0 0;line-height:1.6}
.trk .st.todo b{color:var(--tk-light)}
.trk .st.bad b{color:#b91c1c}
.trk .st.bad p{background:#fdecec;border-radius:9px;padding:9px 12px;color:#991b1b}
@media(prefers-reduced-motion:reduce){.trk .st.now::after{animation:none;box-shadow:0 0 0 5px rgba(79,70,229,.22)}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="w-full max-w-[520px] rounded-[15px] border border-tk-off bg-white px-[26px] pt-6 pb-[26px]">
  <div class="mb-[22px] flex items-start justify-between gap-3.5">
    <div><h2 class="text-[1rem] font-bold tracking-[-.01em]">Parcel HB-4471-902</h2><p class="mt-[3px] text-[.79rem] text-tk-muted">Two items · 3.4 kg · Signed for</p></div>
    <span class="rounded-full bg-[#e8f8ee] px-[11px] py-[5px] text-[.74rem] font-extrabold whitespace-nowrap text-[#15803d]">Arrives Thu</span>
  </div>
  <ol class="relative pl-[30px]">
    <li class="relative pb-6 before:absolute before:top-4 before:-bottom-2 before:-left-6 before:w-0.5 before:bg-tk-done after:absolute after:top-[3px] after:-left-[30px] after:size-5 after:rounded-full after:border-3 after:border-tk-done after:bg-tk-done"><b class="block text-[.9rem]"><span class="sr-only">Completed: </span>Order received</b><span class="mt-0.5 block text-[.76rem] text-tk-light tabular-nums"><time datetime="2024-09-02T09:14">Mon 2 Sep, 09:14</time></span></li>
    <li class="relative pb-6 before:absolute before:top-4 before:-bottom-2 before:-left-6 before:w-0.5 before:bg-tk-done after:absolute after:top-[3px] after:-left-[30px] after:size-5 after:rounded-full after:border-3 after:border-tk-done after:bg-tk-done"><b class="block text-[.9rem]"><span class="sr-only">Completed: </span>Packed at Reading depot</b><span class="mt-0.5 block text-[.76rem] text-tk-light tabular-nums"><time datetime="2024-09-02T16:40">Mon 2 Sep, 16:40</time></span></li>
    <li class="relative pb-6 before:absolute before:top-4 before:-bottom-2 before:-left-6 before:w-0.5 before:bg-tk-off after:absolute after:top-[3px] after:-left-[30px] after:size-5 after:rounded-full after:border-3 after:border-tk-bad after:bg-tk-bad"><b class="block text-[.9rem] text-[#b91c1c]"><span class="sr-only">Problem: </span>Delivery attempted</b><span class="mt-0.5 block text-[.76rem] text-tk-light tabular-nums"><time datetime="2024-09-04T11:02">Wed 4 Sep, 11:02</time></span>
      <p class="mt-1.5 text-[.81rem] leading-[1.6] rounded-[9px] bg-[#fdecec] px-3 py-[9px] text-[#991b1b]">Nobody available to sign. The driver left a card and will retry automatically.</p></li>
    <li class="relative pb-6 before:absolute before:top-4 before:-bottom-2 before:-left-6 before:w-0.5 before:bg-tk-off after:absolute after:top-[3px] after:-left-[30px] after:size-5 after:rounded-full after:border-3 after:border-tk-now after:bg-white after:animate-trk-pulse motion-reduce:after:animate-none motion-reduce:after:shadow-[0_0_0_5px_rgba(79,70,229,.22)]" aria-current="step"><b class="block text-[.9rem]"><span class="sr-only">Current step: </span>Out for delivery again</b><span class="mt-0.5 block text-[.76rem] text-tk-light tabular-nums"><time datetime="2024-09-05T07:20">Thu 5 Sep, 07:20</time></span>
      <p class="mt-1.5 text-[.81rem] leading-[1.6] text-tk-muted">On vehicle 41, currently 6 stops away.</p></li>
    <li class="relative after:absolute after:top-[3px] after:-left-[30px] after:size-5 after:rounded-full after:border-3 after:border-tk-off after:bg-white"><b class="block text-[.9rem] text-tk-light"><span class="sr-only">Upcoming: </span>Delivered</b><span class="mt-0.5 block text-[.76rem] text-tk-light tabular-nums">Expected before <time datetime="2024-09-05T18:00">18:00</time></span></li>
  </ol>
</div>
CSS
@theme {
  --color-tk-done: #16a34a;
  --color-tk-now: #4f46e5;
  --color-tk-bad: #dc2626;
  --color-tk-off: #e3e8f2;
  --color-tk-muted: #5f6a86;
  --color-tk-light: #646f8b;
  --animate-trk-pulse: trk-pulse 2s ease-out infinite;
  @keyframes trk-pulse {
    0% { box-shadow: 0 0 0 0 rgba(79,70,229,.45); }
    70% { box-shadow: 0 0 0 9px rgba(79,70,229,0); }
    100% { box-shadow: 0 0 0 0 rgba(79,70,229,0); }
  }
}

About this set

A parcel tracking card with a vertical timeline of delivery events, including a failed attempt, the live current step and the expected delivery. The header shows the parcel number, a short description and an Arrives Thu badge. Below it, completed events have filled green dots on a green spine, the failed attempt has a red dot and a tinted message box explaining what happened, the current step has an indigo ring with a slow pulse, and the final step is grey with its expected time. Use it on an order page, a delivery notification or a support view where the customer needs the history and the next step at a glance. The events are an ordered list, and each one starts with visually hidden text, Completed, Problem, Current step or Upcoming, so the state is spoken rather than left to colour; the live step also has aria-current=”step”. Every timestamp is a time element with a full ISO date and time. The pulse stops and becomes a static halo under reduced motion. The Bootstrap version uses .card and .badge, and the Tailwind version defines the pulse as an –animate theme value.

What’s included

  • Completed, problem, current and upcoming states
  • Exception step with an explanatory message box
  • Pulsing current step that turns static under reduced motion
  • State in visually hidden text plus aria-current="step"
  • ISO timestamps in time elements

Accessibility

Events are an ordered list; each label is prefixed with visually hidden state text such as Problem: or Current step:, and the live event has aria-current="step", so state is never conveyed by colour alone. Dots and spine are CSS decoration. The pulse animation is removed under prefers-reduced-motion, and all text meets 4.5:1.

Customise it

Change --tk-done, --tk-now, --tk-bad and --tk-off on .trk for the states and --tk-muted or --tk-light for secondary text (plain CSS and Bootstrap), or the --color-tk-* theme colours and --animate-trk-pulse in Tailwind.