webdevpuneet.com

Upload Progress — Free HTML CSS JS Snippet

Upload Progress · Loaders · Plain HTML, CSS & JS · Live preview

CategoryLoaders

Copy & paste

Source Code

<div class="wrap">

  <!-- Drop zone -->
  <input type="file" id="file-input" multiple style="display:none" onchange="onFiles(this.files)">
  <div class="dropzone" id="dz" onclick="triggerPick()" ondragover="onDragOver(event)" ondragleave="onDragLeave(event)" ondrop="onDrop(event)">
    <svg class="dz-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
    <div class="dz-text">Drop files here or <span class="dz-link">browse</span></div>
    <div class="dz-sub">PNG, JPG, PDF, ZIP — up to 25 MB</div>
  </div>
  <button class="demo-btn" onclick="runDemo()">▶ Try demo upload</button>

  <!-- File list -->
  <div class="file-list" id="file-list"></div>

  <!-- Overall summary -->
  <div class="summary hidden" id="summary">
    <div class="summary-row">
      <span id="sum-label">Uploading 3 files…</span>
      <span id="sum-pct">0%</span>
    </div>
    <div class="sum-bar"><div class="sum-fill" id="sum-fill"></div></div>
  </div>

</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), 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

Drag-and-drop zone: dragover/dragleave/drop events, .drag-over visual feedback class
Click-to-browse: hidden input.click() via triggerPick()
File type icon map: emoji + coloured background per extension (img/pdf/zip/other)
File size formatter: bytes → KB or MB with one decimal place
Per-file progress bar: independent setInterval per file, variable simulated speed
Remove button: removes file item from DOM
Overall summary bar: average of all fi-fill widths, auto-update on each tick
Done state: "✓ Done" text, green .done class when pct reaches 100

About this UI Snippet

Upload Progress — Drag-and-Drop Zone, Per-File Progress Bars, Type Icons & Summary Bar

Screenshot of the Upload Progress snippet rendered live

A file upload interface with drag-and-drop, per-file progress bars, and a summary progress is one of the most complex UI patterns developers need to build from scratch. This snippet provides everything: a drag-and-drop zone with visual hover feedback (see also the standalone file dropzone), multiple file selection via click or drop, per-file progress bars with file type icons and formatted sizes, individual done/error states, a remove button, and an overall summary progress bar with a completion message — all in plain HTML, CSS, and vanilla JavaScript.

The drag-and-drop zone

The dropzone listens for three events: ondragover (calls e.preventDefault() to allow drop and adds .drag-over for visual feedback), ondragleave (removes .drag-over), and ondrop (calls e.preventDefault(), removes .drag-over, and passes e.dataTransfer.files to onFiles()). Clicking the dropzone triggers the hidden file input via triggerPick(), which calls input.click(). The hidden input has the multiple attribute for multi-file selection.

File type icon detection

The EXT_ICON and EXT_CLASS maps assign emoji icons and coloured background classes based on the file extension. Image extensions get a camera emoji and indigo background. PDF gets a document emoji and red background. ZIP/RAR get a compressed emoji and amber background. Unknown extensions get a folder emoji and grey background.

Per-file progress simulation

Each file gets its own setInterval that increments the progress at a random speed (5–15% per tick, every 200ms). This simulates variable upload speeds per file. In production, replace the simulation with XHR upload.onprogress events that fire with real loaded/total values. When pct reaches 100, the interval clears, the status text changes to "✓ Done", and the class changes to .done (green).

Overall summary bar

The updateOverallPct() function reads all .fi-fill bar widths in the file list and averages them. The average percentage fills the summary bar and updates the summary percentage text. When all files complete, updateSummary() changes the label to "✓ All files uploaded".

File size formatting

fmtSize() formats bytes to KB (if under 1MB) or MB (if 1MB or above) with one decimal place — matching the file size display convention used by operating systems and most upload interfaces. The formatter correctly handles file sizes from bytes through kilobytes to megabytes, with one decimal place for readability without verbosity at smaller file sizes.

Step by step

How to Use

  1. 1
    Click the drop zone or drag files onto itClick the zone to open the system file picker (multiple files allowed). Or drag files from your desktop onto the zone — it highlights with an indigo border and subtle background when a drag enters.
  2. 2
    Watch per-file progress and the overall summary barEach file shows its own progress bar, percentage, and status. The overall summary bar averages all file progress. When all files complete, the summary label changes to "✓ All files uploaded".
  3. 3
    Wire to a real upload endpointReplace the setInterval simulation in onFiles() with a real XHR upload: xhr.upload.onprogress = e => { pct = (e.loaded/e.total)*100; fill.style.width = pct+"%" }. Start the XHR with xhr.open("POST","/upload"); xhr.send(formData).
  4. 4
    Remove files from the listClick the × button on any file to remove it from the list. In a real upload, also cancel the in-progress XHR: xhr.abort(). Remove the file from the queued upload list.
  5. 5
    Add file size and type validationIn onFiles(), filter files before processing: const valid = [...files].filter(f => f.size <= 25*1024*1024 && allowedTypes.includes(ext(f.name))). Show an error message for rejected files: create a .file-item with .fi-status.error instead of a progress bar.
  6. 6
    Export in your formatClick "HTML" for a standalone file, "JSX" for a React component using useState for the files array and useRef for XHR instances, or "Tailwind" for a Tailwind CSS version.

Real-world uses

Common Use Cases

Multi-file document and image upload interfaces
The per-file progress bar pattern is standard for document management systems, image upload flows, and media asset managers. Users can see each file progressing independently and remove specific files before the upload completes.
Form attachment upload for support tickets and applications
Add the upload zone to a support ticket form or job application. Show per-file progress bars so users know their attachments are uploading before they submit the form. Disable the submit button until all files reach 100%.
Cloud storage and file sharing upload interfaces
The drag-and-drop zone with type icons, formatted sizes, and individual progress bars matches the interaction pattern of Google Drive, Dropbox, and OneDrive uploads — pair it with the file manager UI for browsing uploaded files. Users expect this pattern and find it immediately familiar.
Wire to S3, Cloudinary, or custom upload APIs
For S3: get a pre-signed URL from your backend, then PUT the file directly to S3 via XHR. The XHR upload.onprogress event fires with loaded/total values. For Cloudinary: use their upload API with XMLHttpRequest for progress events. Both require only changing the URL and method in the XHR call.
Study drag-and-drop file API and XHR upload progress events
The dropzone demonstrates the three drag event handlers needed for file drop: dragover (must call preventDefault to enable drop), dragleave (visual cleanup), and drop (reads e.dataTransfer.files). The XHR upload.onprogress pattern shows how to get real upload progress from the browser.
Batch import and data migration upload tools
Use for CSV, Excel, or JSON import interfaces where users upload multiple data files. Show per-file validation status (valid CSV, invalid format) alongside the progress bar. The summary bar communicates overall import progress for large batch operations.

Got questions?

Frequently Asked Questions

Replace the setInterval simulation for each file with a real XHR upload: const xhr = new XMLHttpRequest(); xhr.upload.addEventListener("progress", e => { if (e.lengthComputable) { const pct = Math.round(e.loaded/e.total*100); fill.style.width = pct+"%"; status.textContent = pct+"%"; updateOverallPct(list, total); } }); xhr.addEventListener("load", () => { status.textContent = "✓ Done"; status.className = "fi-status done"; done++; updateSummary(done, total); }); xhr.open("POST", "/api/upload"); const fd = new FormData(); fd.append("file", f); xhr.send(fd).

In onFiles(), filter before the forEach: const valid = [...files].filter(f => { const e = ext(f.name); return allowedExts.includes(e) && f.size <= MAX_SIZE; }); const invalid = [...files].filter(f => !valid.includes(f)). For invalid files, create a .file-item with a red error status span instead of a progress bar: status.textContent = "File too large" or "Invalid type". Append these error items to the list alongside valid items.

Store each XHR in a Map keyed by file item ID: const xhrMap = new Map(); xhrMap.set(id, xhr) after creating the XHR. In removeItem(), call xhrMap.get(id)?.abort() before removing the DOM element and deleting from the Map. This stops the upload and prevents the progress callback from firing after the element is removed.

Click "JSX" to download. Manage files as an array in useState: [{id, name, size, pct, status}]. Add files in the drop/pick handler: setFiles(prev => [...prev, ...newFiles.map(toFileObj)]). Update each file's pct in the XHR progress handler: setFiles(prev => prev.map(f => f.id === id ? {...f, pct} : f)). Compute overall progress with useMemo: files.reduce((sum,f) => sum+f.pct, 0) / files.length.