data-upload

Enhance a file input with a drag-and-drop zone, file list display, and browse button. Works with native accept and multiple attributes.

Overview

The data-upload attribute enhances a native <input type="file"> with a drag-and-drop zone, visual file list, and styled prompt. No wrapper element needed — just add the attribute to the file input.

<label for="avatar">Profile photo</label> <input type="file" id="avatar" data-upload>

How It Works

Add data-upload to any <input type="file">. The init script:

  1. Creates a .upload-zone wrapper with role="presentation" around the input
  2. Adds a .upload-prompt with contextual text based on accept and multiple attributes
  3. Creates a .upload-file-list (<ul> with aria-label="Selected files") to display chosen files
  4. Listens for drag events (dragenter, dragleave, dragover, drop) on the zone
  5. Sets data-dragover on the zone during active drag for visual feedback
  6. Clicking anywhere in the zone triggers the hidden native file input
  7. On file selection, renders file names and sizes in the file list

The underlying input remains a real form control. It submits with the form, respects accept and multiple, and fires native change events.

Attributes

Attribute Type Description
data-upload boolean Enables the drag-and-drop zone enhancement on the file input.
data-upload-init boolean Set automatically to prevent double-binding. Do not set manually.
accept string Native attribute. Filters allowed file types. Reflected in the prompt text.
multiple boolean Native attribute. Allows selecting multiple files. Changes prompt to plural text.

DOM Structure

After initialization, the file input is wrapped in the following structure:

<!-- Before init --> <input type="file" id="avatar" data-upload> <!-- After init --> <div class="upload-zone" role="presentation"> <div class="upload-prompt"> <p>Drop file here or click to browse</p> </div> <input type="file" id="avatar" data-upload data-upload-init> <ul class="upload-file-list" aria-label="Selected files"></ul> </div>

The native <input type="file"> is visually hidden but remains in the DOM for form submission and accessibility.

Accept Types

Use the native accept attribute to restrict file types. The prompt text updates to reflect allowed types. Dragged files that do not match the accept filter are rejected.

<label for="document">Upload document</label> <input type="file" id="document" data-upload accept=".pdf,.doc,.docx">

Multiple Files

Add the native multiple attribute to allow selecting more than one file. The file list displays all selected files with individual entries.

<label for="gallery">Upload images</label> <input type="file" id="gallery" data-upload accept="image/*" multiple>

With Form Field

Wrap in <form-field> for validation feedback, helper text, and required indicators.

PDF or Word document, max 5 MB.
<form-field> <label for="resume">Resume</label> <input type="file" id="resume" data-upload accept=".pdf,.doc,.docx" required> <small slot="help">PDF or Word document, max 5 MB.</small> </form-field>

Drag States

During a drag operation, the data-dragover attribute is set on the .upload-zone element. Use it in CSS to provide visual feedback indicating the zone is an active drop target.

  • dragenter — sets data-dragover when a file enters the zone
  • dragleave — removes data-dragover when the file leaves the zone
  • dragover — prevents default to enable drop
  • drop — processes dropped files and removes data-dragover

Events

The file input fires the native change event when files are selected via browse or drag-and-drop. Access the files from e.target.files.

const input = document.querySelector('[data-upload]'); input.addEventListener('change', (e) => { const files = e.target.files; for (const file of files) { console.log(file.name, file.size, file.type); } });

You can also listen for drag events directly on the .upload-zone element:

const zone = document.querySelector('.upload-zone'); zone.addEventListener('dragenter', () => { console.log('File dragged over zone'); }); zone.addEventListener('drop', (e) => { console.log('Files dropped:', e.dataTransfer.files.length); });

Styling

The upload zone, prompt, and file list are fully customizable via CSS. All styles target the generated class names:

/* Default zone styling */ .upload-zone { border: 2px dashed var(--color-border); border-radius: var(--radius-m); padding: var(--size-xl); text-align: center; transition: border-color 0.15s, background-color 0.15s; cursor: pointer; } /* Drag-over state */ .upload-zone[data-dragover] { border-color: var(--color-primary); background-color: var(--color-primary-subtle); } /* Prompt text */ .upload-prompt { color: var(--color-text-muted); } /* File list */ .upload-file-list { list-style: none; padding: 0; margin-top: var(--size-s); }

The dashed border and background color change during drag-over, providing clear visual feedback. The file list renders below the prompt with file names and formatted sizes.

Dynamic Elements

File inputs added to the DOM after page load are automatically enhanced via a MutationObserver. No manual initialization is needed.

// Dynamically added upload inputs are auto-enhanced via MutationObserver const input = document.createElement('input'); input.type = 'file'; input.dataset.upload = ''; input.accept = 'image/*'; document.body.appendChild(input); // input is ready to use — no manual init needed

Accessibility

  • role="presentation" on the zone wrapper prevents it from being announced as an interactive element
  • aria-label="Selected files" on the file list gives screen readers context for the displayed file names
  • The native <input type="file"> remains in the DOM, preserving keyboard access and screen reader announcements
  • Clicking or pressing Enter/Space on the zone opens the native file picker dialog
  • A visible <label> is required for the file input
  • accept restrictions are enforced on both browse and drop to prevent invalid file selection
  • Without JavaScript, the native file input renders normally — progressive enhancement