You Might Also Like
Multi-File Upload Queue — Concurrency-Limited Upload Loader
Multi-File Upload Queue · Loaders · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Multi-File Upload Queue — Limited-Concurrency Uploads with Retry

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.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how fillQueue() enforces MAX_CONCURRENT by counting active files and only pulling from the waiting pool when a slot is free, and why it needs to be called again after both a successful completion and a failure for the queue to keep draining correctly. It's worth an efficiency check too: ask whether recomputing the full average in updateSummary() on every single tick of every active file matters at six files versus sixty, and how you'd batch those updates if it did. For extending it, ask for a version wired to real XMLHttpRequest uploads with genuine upload.onprogress events, a configurable MAX_CONCURRENT exposed as a UI control, or a pause/cancel action per file that correctly frees its slot for the next waiting item. 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:
Build a "multi-file upload queue" in plain HTML, CSS, and JavaScript that enforces a maximum number of concurrent uploads — no drag-and-drop UI needed, focus purely on the queue mechanics.
Requirements:
- A list of several files (name, size, icon) each rendered as a row with its own progress bar and a status label that can read Waiting, an active percentage, Done, or Failed.
- A JavaScript state array tracking each file's status (waiting, active, done, or error) and progress percentage, with a constant limiting how many files may be in the active status simultaneously (e.g. 2).
- A queue-filling function that counts currently active files and starts new ones from the waiting pool only until the concurrency limit is reached, and must be re-invoked every time any file finishes or fails, so a freed slot is immediately backfilled by the next waiting file — files must not all start simultaneously regardless of the limit.
- Each active file must progress independently via its own interval timer with a randomized speed, so that two files running concurrently visibly complete at different, unscripted times rather than in lockstep.
- Exactly one file must be scripted to fail partway through its first upload attempt (after passing some progress threshold), switching its row to a distinct error state with an inline Retry button; clicking Retry must restart that file's upload from zero and allow it to re-enter the concurrency-limited active pool, succeeding on the second attempt.
- A top-level summary section must show a live aggregate progress bar and percentage computed as the true running average of every file's current individual progress (not a separate faked value), plus a text line breaking down how many files are currently active, waiting, done, and failed, all updating in real time as the queue drains.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
- 1Paste HTML, CSS, and JSSix files render, two begin uploading immediately, the rest sit "Waiting".
- 2Watch the queue advanceAs each active file finishes, the next waiting file automatically starts.
- 3See the scripted failureOne file fails partway through and shows a Retry button.
- 4Click RetryThat file restarts from zero and succeeds on its second attempt.
- 5Watch the summary barThe top bar and meta line track the true live average and counts.
- 6Run it againOnce every file settles, click "Run queue again" to reset and replay.
Real-world uses
Common Use Cases
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.