Source Code

<div class="container py-5 d-flex justify-content-center">
  <div class="card bsqueue-card">
    <div class="card-body p-4">
      <div class="d-flex justify-content-between align-items-center mb-2">
        <label class="form-label small fw-semibold mb-0">Upload files</label>
        <span class="small text-muted" id="bsqueueSummary"></span>
      </div>
      <input type="file" class="form-control mb-3" id="bsqueueInput" multiple>
      <ul class="list-unstyled mb-0" id="bsqueueList"></ul>
    </div>
  </div>
</div>

Bootstrap Multi-File Upload Queue — Free HTML CSS JS Snippet

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

What's included

Features

Every file uploads through its own independent progress state and timer, not a shared value
Cancelling or removing one file has zero effect on any other file's upload in progress
One action button per row correctly switches between Cancel and Remove based on that file's own status
A live summary count is recomputed from the files array on every change, never tracked separately
New files can be added to the queue at any time, even while others are actively uploading

About this UI Snippet

Bootstrap Multi-File Upload Queue — HTML, CSS & JavaScript

Screenshot of the Bootstrap Multi-File Upload Queue snippet rendered live

Every selected file becomes its own independent object in a files array — { id, file, progress, status, timer } — with its own setInterval started by runUpload(f). That per-file timer is the key design decision: uploading five files at once means five completely independent intervals ticking at their own randomized pace, so one file finishing early or being cancelled has zero effect on any other file's progress, unlike a single shared progress value trying to represent multiple uploads at once.

Each row's action button does double duty by design — it reads "Cancel" while status === 'uploading' and "Remove" once a file is done or failed, and the click handler branches on that same status to decide whether to mark the file failed (stopping its timer) or delete it from the list entirely. That single button covers the two related-but-different real actions a queue row needs without needing two separate buttons competing for space in a compact row.

updateSummary() counts files.filter(f => f.status === 'done').length fresh every time a row changes, rather than incrementing a separate counter — so the running "X of Y uploaded" total can never drift from what the individual rows are actually showing, the same source-of-truth pattern used throughout this collection's other multi-item components.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Hand this snippet to an AI coding assistant like Claude and ask it to add a global "Cancel all" action that stops every currently uploading file at once, or to limit how many files upload concurrently (e.g. 3 at a time) with the rest waiting in a visibly queued state until a slot frees up.

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 Bootstrap 5.3 multi-file upload queue, using the real Bootstrap CDN framework (bootstrap.min.css and bootstrap.bundle.min.js), not custom CSS made to resemble it.

Requirements:
- A file input with the multiple attribute. Selecting several files at once adds one row per file to a list, each with its own progress bar.
- Each file must upload independently with its own simulated progress interval — cancelling or completing one file must have zero effect on any other file's progress.
- Each row's single action button must show "Cancel" while that file is uploading, and switch to "Remove" once it's done or failed; clicking it should behave accordingly (stop and mark failed, or delete the row entirely).
- Show a live summary above the list, e.g. "2 of 4 uploaded", recomputed from the current state of all files rather than tracked as a separate counter.
- New files can be added to the queue at any time, including while other files are still actively uploading.

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
    Select several files at onceEach one gets its own row with an independent progress bar, all uploading in parallel.
  2. 2
    Watch the summary lineIt updates live as each file individually finishes, e.g. "2 of 4 uploaded".
  3. 3
    Click "Cancel" on one file mid-uploadOnly that row stops and turns red — every other file keeps uploading completely unaffected.
  4. 4
    Click "Remove" on the cancelled rowIt disappears from the list and from the summary count entirely.
  5. 5
    Select more files while others are still uploadingThe new files queue up as additional independent rows without disturbing the ones already in progress.

Real-world uses

Common Use Cases

Bulk document or photo upload forms
The natural next step from bootstrap-file-upload-progress once a form needs to accept more than one file per submission.
Media libraries and asset management tools
Uploading a batch of assets at once with independent, cancellable progress per file.
Learning independent per-item async state
A clean example of managing several unrelated async operations at once without them interfering with each other.

Got questions?

Frequently Asked Questions

Yes — each file gets its own setInterval the instant it's added, with no queueing or waiting for a previous file to finish, which mirrors how a real implementation using several concurrent fetch or XHR requests would behave.

Cancelling stops its timer and marks it failed (turning the bar red); removing afterward clears its timer again defensively and deletes it from both the files array and the rendered list.

Yes. Store files as an array in component state, keep each file's interval reference in a ref-backed map keyed by id (to avoid re-creating intervals on re-render), and derive the summary count from the same array on every render.

Replace the setInterval simulation inside runUpload() with a real per-file XMLHttpRequest or fetch upload, updating that file's progress from its own upload.onprogress event — the surrounding row rendering and cancel/remove logic needs no changes.