webdevpuneet.com

Multi-File Upload Queue — Concurrency-Limited Upload Loader

Multi-File Upload Queue · Forms · Plain HTML, CSS & JS · Live preview

CategoryForms

Copy & paste

Source Code

<div class="fq-card">
  <div class="fq-summary">
    <div class="fq-summary-row">
      <span class="fq-summary-label" id="fqLabel">Uploading 6 files…</span>
      <span class="fq-summary-pct" id="fqPct">0%</span>
    </div>
    <div class="fq-summary-track"><div class="fq-summary-fill" id="fqFill"></div></div>
    <div class="fq-summary-meta" id="fqMeta">2 active · 1 queued · 3 waiting</div>
  </div>
  <ul class="fq-list" id="fqList"></ul>
  <button type="button" class="fq-restart" id="fqRestart">↻ Run queue again</button>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Concurrency-limited queue
Only MAX_CONCURRENT files upload at once; the rest wait their turn.
Auto-advancing slots
fillQueue() pulls the next waiting file in the moment a slot frees up.
Independent per-file timers
Each active upload runs its own randomised-speed interval.
Realistic staggered finishes
Files complete at genuinely different, unscripted times.
Real failure and retry
One file errors mid-upload and can be retried back into the queue.
Live true-average summary
The top bar recomputes the real average progress every tick.
Status breakdown
Active, waiting, done, and failed counts update live in the meta line.
Backend-ready structure
Swap the simulated interval for real XHR upload.onprogress with no other changes.

About this UI Snippet

Multi-File Upload Queue — Limited-Concurrency Uploads with Retry

Screenshot of the Multi-File Upload Queue snippet rendered live

Real upload managers rarely fire every file at once — bandwidth and browser connection limits mean only a handful upload concurrently while the rest wait their turn. This snippet models that honestly: six files queue up, only two upload at a time, each running file advances on its own randomised interval so they finish at different moments, one file genuinely fails partway through with a retry action, and a live aggregate bar and status line summarise the whole queue — all in plain HTML, CSS, and vanilla JavaScript, no drag-and-drop chrome, focused purely on the queue mechanics.

A concurrency-limited queue, not a free-for-all

fillQueue() is the core of the snippet: it counts how many files are currently active and starts new ones from the waiting pool only until MAX_CONCURRENT (2) is reached. Every time a file finishes — successfully or with an error — fillQueue() runs again, pulling the next waiting file into an open slot. This is the same limited-concurrency pattern real upload clients use to avoid saturating a connection, and it's a meaningfully different mechanic from a simple "start everything simultaneously" uploader.

Independent, realistically staggered progress

Each active file gets its own setInterval with a randomised speed, so two files running side by side visibly finish at different times — one might complete in three seconds, another in eight. Nothing is synchronised or faked to look tidy; the raw randomness is what makes the queue feel like real network activity rather than a scripted animation.

A genuine failure and retry

One file (ERROR_INDEX) is scripted to fail once it passes 40% progress on its first attempt, switching to a red error row with an inline Retry button. Clicking Retry calls startFile again for that index, which restarts its progress from zero and re-enters the active pool — this time it's allowed to succeed, demonstrating a real state transition from error back to active rather than a decorative error icon that never resolves.

A live, honest aggregate

The top summary bar isn't a rough estimate — updateSummary() recomputes the true average of every file's current progress (treating a failed file as 0% until it's retried) on every tick, plus a live breakdown of how many files are active, waiting, done, or failed. This mirrors exactly what a user needs to know at a glance: how far along is the whole batch, and is anything stuck.

Wiring it to a real backend

Replace startFile's simulated interval with a real XMLHttpRequest (or fetch with a readable stream) per file, updating s.progress from upload.onprogress's loaded/total, and call fillQueue() from that request's load/error handlers instead of the simulated completion. The concurrency-limiting, retry, and summary logic all stay exactly the same. Pair it with the drag-and-drop upload progress pattern for the initial file-picking step, or a loading overlay while the queue completes.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSSix files render, two begin uploading immediately, the rest sit "Waiting".
  2. 2
    Watch the queue advanceAs each active file finishes, the next waiting file automatically starts.
  3. 3
    See the scripted failureOne file fails partway through and shows a Retry button.
  4. 4
    Click RetryThat file restarts from zero and succeeds on its second attempt.
  5. 5
    Watch the summary barThe top bar and meta line track the true live average and counts.
  6. 6
    Run it againOnce every file settles, click "Run queue again" to reset and replay.

Real-world uses

Common Use Cases

Media and asset upload managers
Handle batch uploads of large files with only two running at once, while the remaining files wait their turn in the queue.
Backup and sync dashboards
Show a web view of a sync tool's activity, with independent per-file progress and an aggregate summary of what has finished.
Bulk document ingestion
Compare with a single upload progress bar when many documents arrive together and need individual retry.
CMS and DAM bulk imports
Import many assets while capping concurrency, with fillQueue() pulling the next waiting file the moment a slot opens.
Rate-limited API uploads
Respect a backend's concurrency limit, adapting the same pattern for data pipeline job queues, with a real failure and a retry button.
Related: Infinite Scroll Loading Spinner
See the Infinite Scroll Loading Spinner for a related loaders pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

fillQueue() counts files currently in the active status and starts more from the waiting pool only until MAX_CONCURRENT is reached. It's called again every time a file finishes or errors, so a freed slot is immediately backfilled from the queue — the same mechanic real upload managers use to avoid overwhelming a connection.

Each active file runs its own setInterval with a randomised per-tick speed, so two files started at the same moment naturally diverge — one may finish well before the other. Nothing is synchronised on purpose; the unscripted variance is what makes the queue read as real network activity.

One file is scripted to fail once past 40% progress on its first attempt, switching to an error row with a Retry button. Clicking Retry calls startFile again for that index, resetting its progress to zero and re-entering the active pool exactly like a fresh upload — this time it's allowed to complete, so the failure-to-success transition is real, not decorative.

It's a real average. updateSummary() sums every file's current progress (a failed file counts as 0% until retried) and divides by the total file count on every tick, so the top bar and percentage always reflect the true, live state of every row — not a smoothed or faked approximation.

Replace startFile's setInterval with a real XMLHttpRequest per file: update s.progress from xhr.upload.onprogress's loaded/total, and call fillQueue() from the request's load and error handlers instead of the simulated completion. Keep MAX_CONCURRENT, the retry button, and updateSummary unchanged — they operate purely on the state array regardless of what drives it.