Source Code

<div class="dot-phone">
  <div class="dot-screen">
    <div class="dot-status"><span>9:41</span><span class="dot-batt"><i></i></span></div>

    <header class="dot-head">
      <button class="dot-back" aria-label="Back">&#8249;</button>
      <b>Order #4821</b>
      <button class="dot-help" aria-label="Help">?</button>
    </header>

    <div class="dot-map">
      <div class="dot-map-grid"></div>
      <div class="dot-route-line"></div>
      <div class="dot-pin dot-pin-store">&#127974;</div>
      <div class="dot-pin dot-pin-rider" id="dotRiderPin">&#128692;</div>
      <div class="dot-pin dot-pin-home">&#127968;</div>
    </div>

    <div class="dot-sheet">
      <div class="dot-eta">
        <div>
          <b id="dotEtaMinutes">18 min</b>
          <span>Estimated arrival</span>
        </div>
        <span class="dot-eta-badge" id="dotEtaBadge">On the way</span>
      </div>

      <div class="dot-steps" id="dotSteps">
        <div class="dot-step done" data-step="0">
          <span class="dot-step-dot"></span>
          <div><b>Order confirmed</b><small>12:04 PM</small></div>
        </div>
        <div class="dot-step done" data-step="1">
          <span class="dot-step-dot"></span>
          <div><b>Preparing your order</b><small>12:07 PM</small></div>
        </div>
        <div class="dot-step active" data-step="2">
          <span class="dot-step-dot"></span>
          <div><b>Rider picked up your order</b><small id="dotStep2Time">12:16 PM</small></div>
        </div>
        <div class="dot-step" data-step="3">
          <span class="dot-step-dot"></span>
          <div><b>Out for delivery</b><small>&ndash;</small></div>
        </div>
        <div class="dot-step" data-step="4">
          <span class="dot-step-dot"></span>
          <div><b>Delivered</b><small>&ndash;</small></div>
        </div>
      </div>

      <div class="dot-rider-card">
        <span class="dot-rider-avatar">M</span>
        <div class="dot-rider-info">
          <b>Marcus</b>
          <span>Your rider &middot; &#9733; 4.9</span>
        </div>
        <button class="dot-rider-msg" id="dotMsgBtn" aria-label="Message rider">&#128172;</button>
        <button class="dot-rider-call" id="dotCallBtn" aria-label="Call rider">&#128222;</button>
      </div>
    </div>
  </div>
</div>

Mobile Delivery Order Tracking Screen — Free Snippet

Mobile Delivery Order Tracking Screen · Mobile · Plain HTML, CSS & JS · Live preview

What's included

Features

Five-step delivery timeline fully derived from one currentStep number via renderSteps()
Pulsing dot indicator appears only on the currently active step
Rider map pin animates its position with a CSS transition rather than teleporting
ETA counts down continuously on its own interval, independent of step changes
One advanceStep() function keeps the timeline, map pin, and ETA badge in sync
Working message and call buttons with a brief tap-confirmation state
Delivered state relabels the ETA badge and freezes the countdown automatically
Zero dependencies, vanilla JavaScript only

About this UI Snippet

Mobile Delivery Tracking Screen — Live Timeline, Moving Pin & Countdown ETA

Screenshot of the Mobile Delivery Order Tracking Screen snippet rendered live

A delivery tracking screen is checked compulsively in short bursts — a user opens the app, glances at the ETA, closes it, and repeats a few minutes later. This snippet builds that screen so each glance shows real movement: a rider pin that slides across a simplified map, a status timeline that advances through real steps rather than sitting frozen, and an ETA number that actually counts down between checks.

A timeline built from steps, not five hardcoded divs

The steps array defines the five delivery stages in order, and renderSteps() walks every .dot-step element, comparing its data-step index against currentStep to decide whether it should render as done (filled dot, solid connector), active (pulsing dot), or upcoming (hollow, muted). Advancing the order is just incrementing one number and re-running this comparison — the visual state of all five steps is fully derived from currentStep, never set individually.

A pulsing dot only on the current step

Only .dot-step.active .dot-step-dot gets the box-shadow pulse animation — completed steps get a solid filled dot with no animation, and future steps stay hollow. This draws the eye to exactly one place: whatever is happening right now, not the full history or the steps yet to come.

A rider pin that actually moves

#dotRiderPin starts positioned at the store pin's location with transition: left 4s linear already set in CSS. When advanceStep() reaches the "Out for delivery" step, its left value is changed to the destination's position, and the browser animates the move over four seconds — a small but important detail, since a pin that simply teleports across the map reads as fake in a way a slow, steady glide does not.

An ETA that counts down between status changes

tickEta() runs on a setInterval independent of the step timeline, decrementing etaMinutes by one every few seconds (standing in for real-time minutes) as long as the order has not yet reached the final "Delivered" step. This mirrors the real behavior users expect — the number keeps ticking down continuously, not just jumping when a new status arrives.

Two-stage status advancement via simulated push events

setTimeout(advanceStep, ...) is called twice with different delays, standing in for two separate real-time push notifications a delivery backend would send ("picked up" then "delivered"). Each call to advanceStep() is the single function responsible for moving the timeline, the map pin, and the ETA badge text together, so the three never fall out of sync with each other.

Working rider-contact buttons

The message and call buttons each show a brief checkmark confirmation on click before reverting to their icon — a lightweight acknowledgement pattern rather than a dead decorative icon, ready to be wired to a real chat thread or a masked-number calling API.

Wiring it to a real delivery backend

Replace the setTimeout-based advanceStep() calls with a WebSocket or polling connection to your order-status API, and replace the fixed left percentage jump on the rider pin with real GPS coordinates projected onto an actual map library (Mapbox GL, Google Maps, or MapLibre) instead of the simplified grid background.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Rather than tracing the derived-timeline logic by hand, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how renderSteps() derives every step's visual state from the single currentStep variable, and how advanceStep() keeps the timeline, the map pin position, and the ETA badge text synchronized from one function call. The same assistant can help you optimize it, for instance asking whether the ETA countdown should pause automatically when the tab loses focus so it doesn't drift out of sync with server time. It is also useful for extending the screen: ask it to wire in a real WebSocket connection for live status pushes, integrate an actual map library with real rider GPS coordinates in place of the simplified grid, or add a cancel-order flow with its own confirmation step. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a mobile "delivery order tracking" screen in plain HTML, CSS, and JavaScript, framed inside a CSS phone mockup, no map library or backend required for the demo.

Requirements:
- A simplified map area at the top showing a store pin, a home/destination pin, a dashed route line between them, and a separate rider pin that starts at the store's position.
- Below the map, a bottom sheet showing a live ETA in minutes with a status badge, and a five-step vertical timeline (order confirmed, preparing, picked up, out for delivery, delivered) where every step's visual state (completed, currently active with a pulsing indicator, or upcoming) is derived entirely from one numeric "current step" variable rather than toggled individually per step.
- The ETA minutes must count down automatically on its own timer while the order is in progress, and stop changing once the final step is reached.
- Advancing to the "out for delivery" step must animate the rider pin's position from the store toward the destination using a CSS transition (not an instant jump), and reaching "delivered" must update the status badge's text and color.
- Include a rider info card with working message and call buttons that each show a brief tap-confirmation state before reverting to their normal icon.
- Simulate timeline progression with a couple of delayed function calls standing in for real-time push notifications from a delivery backend.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA tracking screen renders with a simplified map, a moving rider pin, and a five-step delivery timeline.
  2. 2
    Watch the ETA count downThe estimated arrival number decreases automatically every few seconds while the order is in progress.
  3. 3
    Wait for the timeline to advanceAfter a few seconds, the rider pin glides toward the destination and the "Out for delivery" step activates, then "Delivered".
  4. 4
    Tap the message or call buttonEach shows a brief checkmark confirmation before reverting to its normal icon.
  5. 5
    Edit the steps arrayChange the step labels or add new ones in the JS panel — renderSteps() picks them up automatically.
  6. 6
    Wire it to a real backendReplace the setTimeout-driven advanceStep() calls with a WebSocket or polling connection to your order-status API, and the pin's CSS position with real GPS coordinates on an actual map library.

Real-world uses

Common Use Cases

Food and grocery delivery apps
The canonical use case — a live order-status screen a customer checks repeatedly between placing an order and it arriving.
Parcel and courier tracking apps
Swap the rider-card labels and step names for courier-specific stages (out for delivery, at local facility, delivered) using the same derived-timeline pattern.
Ride-hailing pickup confirmation screens
Adapt the moving-pin and ETA-countdown pattern for a driver approaching a pickup location instead of a delivery destination.
Teaching derived-timeline UI state
A compact reference for driving a multi-step visual timeline entirely from one numeric state variable instead of toggling each step by hand.
Related: Mobile Boarding Pass Screen
See the Mobile Boarding Pass Screen for a related travel-status mobile screen worth comparing against this one.
Related: Mobile Search Filters Screen
See the Mobile Search Filters Screen for a related mobile commerce screen worth pairing with this one.

Got questions?

Frequently Asked Questions

renderSteps() compares each step element's data-step index against the single currentStep variable: indexes below it get the done class, the matching index gets active, and higher indexes get neither. The entire timeline's visual state is derived from that one number.

No — it is a simulated demo. The pin's CSS left value jumps to a fixed destination percentage when the "out for delivery" step is reached, and a CSS transition animates the move over four seconds. Replace this with real coordinates projected onto an actual map library for production use.

A separate setInterval calls tickEta() on its own schedule, decrementing etaMinutes independently of the step timeline, as long as the order has not yet reached the final Delivered step. This keeps the number actively counting down between status changes, not just jumping when a new step arrives.

Two setTimeout calls invoke advanceStep() at different delays, standing in for real-time push notifications a delivery backend would send. In production, replace these with a WebSocket or polling connection to your order-status API.

Not in this demo — they show a brief checkmark confirmation on click before reverting. Wire them to a real in-app chat thread or a masked-number calling API for actual rider contact.

Yes. Track currentStep and etaMinutes in state, derive each step's done/active class from a comparison against currentStep in a computed value or map, and drive the pin's position from a percentage or coordinate value tied to the same state.