Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
Enhance a file input with a drag-and-drop zone, file list display, and browse button. Works with native accept and multiple attributes.
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>
Add data-upload to any <input type="file">. The init script:
.upload-zone wrapper with role="presentation" around the input.upload-prompt with contextual text based on accept and multiple attributes.upload-file-list (<ul> with aria-label="Selected files") to display chosen filesdragenter, dragleave, dragover, drop) on the zonedata-dragover on the zone during active drag for visual feedbackThe underlying input remains a real form control. It submits with the form, respects accept and multiple, and fires native change events.
| 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. |
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.
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">
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>
Wrap in <form-field> for validation feedback, helper text, and required indicators.
<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>
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 zonedragleave — removes data-dragover when the file leaves the zonedragover — prevents default to enable dropdrop — processes dropped files and removes data-dragoverThe 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);});
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.
File inputs added to the DOM after page load are automatically enhanced via a MutationObserver. No manual initialization is needed.
<section> <h2>Accessibility</h2> <ul> <li><code>role="presentation"</code> on the zone wrapper prevents it from being announced as an interactive element</li> <li><code>aria-label="Selected files"</code> on the file list gives screen readers context for the displayed file names</li> <li>The native <code><input type="file"></code> remains in the DOM, preserving keyboard access and screen reader announcements</li> <li>Clicking or pressing Enter/Space on the zone opens the native file picker dialog</li> <li>A visible <code><label></code> is required for the file input</li> <li><code>accept</code> restrictions are enforced on both browse and drop to prevent invalid file selection</li> <li>Without JavaScript, the native file input renders normally — progressive enhancement</li> </ul> </section> <section> <h2>Related</h2> <ul> <li><a href="/docs/patterns/data/files/">File Display pattern</a> — the <code>[data-file]</code> row / card / status patterns for showing selected, uploaded, or downloadable files outside of an upload zone</li> <li><a href="/docs/attributes/data-accept/"><code>data-accept</code></a> — declarative file-type gating</li> </ul> </section>