DEV3 Technologies home

File drop zone with upload progress

A keyboard-accessible drag-and-drop upload area that queues files, checks size and shows a live progress bar for each upload.

Tailwind CSSUploadsuploaddrag-dropprogressfiles

Live preview

Code

<div id="drop" tabindex="0" role="button" aria-label="Choose files to upload"
     class="rounded-2xl border-2 border-dashed border-slate-300 p-8 text-center transition data-[over=true]:border-indigo-500 data-[over=true]:bg-indigo-50">
  <p class="font-medium text-slate-800">Drag files here or <span class="text-indigo-600 underline">browse</span></p>
  <p class="mt-1 text-xs text-slate-500">Up to 50 MB each</p>
  <input id="file" type="file" multiple class="sr-only" tabindex="-1">
</div>
<ul id="queue" class="mt-4 space-y-2" aria-live="polite"></ul>

How it works

What it solves

Users expect to drop files on a page and see each one move independently. A plain file input hides progress, and many drag-and-drop libraries are heavy for what is a few event handlers.

How it works

The visible zone is a focusable element with role="button" that forwards clicks and Enter or Space to a visually hidden file input, so mouse, touch and keyboard users share one path. During drag, dragenter and dragover set a data-over attribute; the Tailwind data-[over=true] variants then restyle the border and background with no class juggling. Preventing the default on dragover is what allows the drop to fire at all.

Each chosen file becomes a list row, the file name is written with textContent to avoid injecting markup, and the size is checked against a limit before any request. Uploads use XMLHttpRequest because its upload.onprogress event reports bytes sent, which fetch cannot yet do. The row bar width and percentage are updated from loaded / total, and the final status reflects the HTTP result.

Adapting it

  • Point the request at your endpoint and add your CSRF header.
  • Restrict types with the input accept attribute and re-check server side.
  • For very large files, replace the single request with chunked uploads.

Pitfalls

  • The server must validate type and size again; client checks are advisory.
  • Reset the input value after a change so choosing the same file twice still fires an event.
  • Limit concurrency if users drop dozens of files at once.

How to use it

  1. Add the markup and script to a page that has an upload endpoint accepting multipart form data.
  2. Change the URL in xhr.open and the size limit constant.
  3. Return a success status code only once the file is stored.
  4. Add error messages per row if your API returns validation details.

Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.

Related snippets