Source Code

<div class="demo">
  <div class="pipeline-card">
    <div class="pipeline-head">
      <span class="pipeline-title">deploy-web-app #482</span>
      <span class="pipeline-branch">main @ 7f3a2c1</span>
    </div>

    <div class="pipeline-track" id="pipelineTrack">
      <div class="stage" data-stage="build">
        <div class="stage-node"><span class="stage-icon"></span></div>
        <span class="stage-label">Build</span>
      </div>
      <div class="stage-connector"></div>
      <div class="stage" data-stage="test">
        <div class="stage-node"><span class="stage-icon"></span></div>
        <span class="stage-label">Test</span>
      </div>
      <div class="stage-connector"></div>
      <div class="stage" data-stage="staging">
        <div class="stage-node"><span class="stage-icon"></span></div>
        <span class="stage-label">Staging</span>
      </div>
      <div class="stage-connector"></div>
      <div class="stage" data-stage="prod">
        <div class="stage-node"><span class="stage-icon"></span></div>
        <span class="stage-label">Production</span>
      </div>
    </div>

    <div class="pipeline-detail" id="pipelineDetail" role="status" aria-live="polite">Click "Run pipeline" to start a deployment.</div>

    <button class="run-btn" id="runBtn">Run pipeline</button>
  </div>
</div>

Deployment Pipeline Stage Tracker — Sequential CI/CD Stage Visualization with Real Halt-on-Failure

Deployment Pipeline Stage Tracker · Dashboards · Plain HTML, CSS & JS · Live preview

What's included

Features

Genuinely sequential async/await execution — later stages never begin until the current one has finished and been evaluated
A real early return halts the loop on failure, leaving subsequent stages in their untouched default state
Every stage, including the final Production stage, has its own independent chance of failure
Three unambiguous, class-driven visual states per stage: running, done, and failed
Connector lines between stages visually reinforce progress via a sibling selector reacting to a completed stage
role="status" aria-live="polite" detail line announces the current stage and final outcome to screen readers
Retry restarts the entire pipeline cleanly from the first stage, matching real CI/CD retry behavior
Fully self-contained simulation, straightforward to replace with real pipeline status polling

About this UI Snippet

Deployment Pipeline Stage Tracker — Sequential Execution That Actually Halts on Failure

Screenshot of the Deployment Pipeline Stage Tracker snippet rendered live

A pipeline visualization is only trustworthy if it behaves like a real pipeline — later stages must never appear to run after an earlier one has failed. This widget implements that correctly: an async/await loop walks through each stage strictly in order, and a failure at any point stops the loop entirely, leaving every subsequent stage in its untouched, not-yet-run visual state.

A real sequential loop, not four independent animations

runPipeline() is a single async function with a for...of loop over the stages array, await-ing each stage's simulated duration before moving to the next iteration. This is a genuinely sequential execution model — stage 2 (test) only begins after stage 1 (build) has fully finished and been evaluated, mirroring how a real CI/CD pipeline actually runs stages one after another rather than kicking off several visual animations in parallel that merely *look* sequential through staggered CSS delays.

A failure genuinely stops execution, via a real return

Each stage's outcome is checked with an independent Math.random() < 0.18 roll — including for the *final* Production stage, deliberately, since a late-stage failure is a completely realistic scenario worth modeling. When a stage fails, the function calls return immediately after updating that stage's visual state and the status message — this is a genuine early exit from the for...of loop, which means the loop's remaining iterations (the subsequent stages) never execute their own stageEl.classList.add('running') line at all. Those later stages are left in whatever state they started in (their default, un-run appearance), correctly representing "this stage never got the chance to run" rather than any kind of skipped-but-attempted state.

Three distinct, unambiguous visual states per stage

Each .stage node can be running (pulsing dot, indigo border), done (solid green with a checkmark), failed (red), or none of these (its default untouched appearance) — and CSS handles all the styling differences through simple class-presence selectors, with the connecting line between two stages also picking up a green color once the stage *before* it (via the .stage.done + .stage-connector sibling selector) has completed, visually reinforcing forward progress along the track.

Retry starts genuinely fresh, not from the failed stage

Clicking "Retry pipeline" calls runPipeline() again from the top, and resetStages() explicitly clears every stage's running/done/failed classes before the loop restarts — so a retry always re-runs the entire pipeline from Build onward, matching how most real CI/CD systems handle a full pipeline retry rather than attempting to resume mid-pipeline from the point of failure.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to explain exactly why an async function with a for...of loop and await guarantees genuinely sequential execution here, compared to an approach using setTimeout calls with staggered delays that might merely look sequential but could actually race. It's also worth asking for a version that supports a manual "skip stage" or "retry from failed stage" action instead of always restarting the whole pipeline, or one that shows a running duration timer next to the currently active stage.

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 deployment pipeline stage tracker dashboard widget in HTML, CSS and vanilla JavaScript that visualizes a CI/CD pipeline running through several sequential stages, correctly halting if any stage fails — no external libraries.

Requirements:
- A horizontal track of at least four connected stages (e.g. Build, Test, Staging, Production), each shown as a node with a label, connected by lines between consecutive stages.
- Implement the pipeline run as a single async function using a for...of loop with await, so each stage's simulated work must genuinely complete before the next stage begins — not a set of independently-timed animations that merely appear sequential.
- Give every stage, including the very last one, its own independent randomized chance of failure (not just the earlier stages) using something like Math.random() against a threshold.
- If a stage fails, immediately halt the entire pipeline via a real early exit from the loop, leaving every subsequent stage in its default, never-run visual state — do not mark later stages as skipped, attempted, or run them in any way after a failure.
- Give each stage three distinct, clearly differentiated visual states: currently running (with a pulsing or animated indicator), successfully completed (a checkmark, distinct color), and failed (a distinct error color) — plus its default not-yet-started appearance.
- Provide a "Run pipeline" button that starts a run, disables itself while a run is in progress, and offers a "Retry" action after failure or "Run again" after success that fully restarts the pipeline sequence from the first stage. Use an accessible live region to announce the current stage and final outcome.

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
    Click "Run pipeline" and watch it play outEach stage runs strictly in order; roughly an 18% chance per stage means you'll see both full-success and halted-on-failure outcomes.
  2. 2
    Watch a failure specificallyNotice that any stage after the one that failed remains untouched — the pipeline genuinely stops, it doesn't skip ahead.
  3. 3
    Adjust stage durationsChange the values in the STAGE_DURATIONS object to make specific stages run faster or slower in the simulation.
  4. 4
    Adjust the failure rateChange the 0.18 probability in runPipeline() to make failures more or less common during testing.
  5. 5
    Add a new stageAdd a new .stage element with a unique data-stage value plus a connector, and add matching entries to STAGE_DURATIONS and STAGE_LABELS.

Real-world uses

Common Use Cases

DEVOPS
CI/CD Dashboard Widgets
Visualize a deployment pipeline's current stage and outcome on an internal engineering dashboard.
OPS
Release Management Tools
Show release engineers exactly which stage a deployment is on or where it halted.
ADMIN
Build System Status Pages
A reusable pattern for visualizing any multi-stage automated process with sequential dependencies.
EDUCATION
Teaching Sequential Async Execution
A clean example of using async/await with a for...of loop to model genuinely ordered, haltable steps.
Related: Habit Tracker Grid
See the Habit Tracker Grid for a related dashboards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

It genuinely runs in order — a single async function awaits each stage's simulated duration inside a for...of loop before the next iteration begins, so stage 2 literally cannot start executing until stage 1's await has resolved and been evaluated, not just visually staggered CSS animations that merely appear sequential.

They never run at all — a failure triggers an immediate return from inside the loop, which exits the function entirely before any later iteration's stageEl.classList.add('running') line executes, so subsequent stages remain in their default, untouched visual state rather than being marked as skipped or attempted.

Any stage, including the final one, has its own independent Math.random() < 0.18 check — there's no special-casing that makes the last stage always succeed, since a late-stage failure is a completely realistic real-world scenario worth representing.

It calls runPipeline() again, which starts by calling resetStages() to clear every stage's running/done/failed classes back to their defaults, then re-runs the entire sequential loop from the very first stage — a full pipeline restart, not a resume from the point of failure.

Replace the wait() call and the randomized failure check inside the loop with actual polling of your CI/CD provider's API for that stage's real status, awaiting until it reports a terminal (success/failure) state before moving to the next stage in the loop.

Yes — the detail text region has role="status" aria-live="polite" and is updated with a plain-language description ("Running Test…", "Build failed — pipeline stopped.", etc.) at every stage transition, so the pipeline's progress and outcome are announced without requiring the user to visually track the stage nodes.