FileDummy Logo
FileDummy
File Upload & Handling

File Upload Progress Bar: HTML5 + Fetch API Complete Guide

Build a real-time file upload progress bar using HTML5 and XMLHttpRequest. Includes CSS animation and a free 50MB DOCX test file to validate your implementation.

September 17, 20269 min read1,420 views
javascripthtml5xhrprogress-barfrontend

When uploading large assets like documents, videos, or raw datasets, providing visual feedback with an accurate progress bar is essential for user experience. While the modern Fetch API does not expose upload progress events, XMLHttpRequest (XHR) and the browser Streams API provide full control.

Why Fetch API Lacks Upload Progress

The Fetch API was designed around WHATWG Streams. While streaming request bodies are becoming supported in some modern browsers, fetch() still does not offer a standardized onUploadProgress callback.

To track upload bytes sent over the wire, XMLHttpRequest remains the industry standard.

Verified Test Asset.docx

Need a large file to test smooth progress animation? Download our 50MB DOCX test file.

Download 50MB DOCX Sample β†’

Building an Upload Progress Bar Component

Here is a complete, clean implementation using modern TypeScript and Tailwind CSS:

TypeScript
export function uploadWithProgress(
  file: File,
  url: string,
  onProgress: (percent: number, loaded: number, total: number) => void
): Promise<string> {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    // Listen to upload progress events
    xhr.upload.addEventListener('progress', (event) => {
      if (event.lengthComputable) {
        const percent = Math.round((event.loaded / event.total) * 100);
        onProgress(percent, event.loaded, event.total);
      }
    });

    xhr.addEventListener('load', () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.responseText);
      } else {
        reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
      }
    });

    xhr.addEventListener('error', () => reject(new Error('Network error during upload')));
    xhr.addEventListener('abort', () => reject(new Error('Upload aborted by user')));

    const formData = new FormData();
    formData.append('file', file);

    xhr.open('POST', url, true);
    xhr.send(formData);
  });
}

Styling with Tailwind CSS and CSS Transitions

Smooth animations prevent the progress bar from jumping erratically:

HTML
<div class="w-full bg-slate-800 rounded-full h-3 overflow-hidden p-0.5">
  <div
    id="progress-bar"
    class="bg-gradient-to-r from-blue-500 to-indigo-600 h-full rounded-full transition-all duration-200 ease-out"
    style="width: 0%"
  ></div>
</div>

Practical Tips

  • Compute transfer speed: Measure event.loaded - lastLoaded every second to display dynamic upload speed in MB/s and estimated time remaining (ETA).
  • Handle artificial pauses: Browsers fire the final 100% event once all bytes leave the client socket, but the server may take several additional seconds to scan or persist the file before sending the HTTP 200 response. Indicate a "Processing..." state between 100% and response arrival.
NDL

Nguyen Dai Long

Author

Backend Lead β€’ Distributed Systems & Cloud Edge Architecture Specialist

4+ years designing high-throughput file ingestion pipelines, database architectures, and distributed edge storage on Cloudflare R2 & AWS S3. Founder of FileDummy and the NDL Ecosystem.

Was this article helpful?

Click Like to support the author and help other developers discover this guide.

Engineering Discussion & Feedback (0)

Share benchmark results, report edge cases, or ask technical questions.

0/3000

No comments yet. Be the first developer to start the discussion!

Frequently Asked Questions

Why doesn't the Fetch API support upload progress natively?β–Ύ

The Fetch API does not expose upload progress events. For upload progress, use XMLHttpRequest with the xhr.upload.onprogress event. Download progress is available via the Streams API on the response body.

How do I show download progress with the Fetch API?β–Ύ

Use response.body.getReader() and track bytes read versus Content-Length. This works natively in all modern browsers without XHR.

What is a realistic file size for testing an upload progress bar?β–Ύ

A 50MB file takes 8–40 seconds on typical broadband β€” long enough to observe a meaningful progress animation. Use FileDummy's 50MB DOCX sample as an ideal test file.

Related Articles