Dropzone
A box that takes files dropped on it or chosen with a click. The form sends the files.
A box that takes files dropped on it or chosen with a click
Drop images here or click to choose
<div class="dropzone">
<input type="file" accept="image/*" multiple aria-label="Choose images">
<i class="icon icon-upload" aria-hidden="true"></i>
<span>Drop images here or click to choose</span>
</div>
A name on the input sends the files with the form
Drop attachments here or click to choose
<div class="dropzone">
<input type="file" name="attachments" accept="image/*" multiple aria-label="Attachments">
<i class="icon icon-upload" aria-hidden="true"></i>
<span>Drop attachments here or click to choose</span>
</div>
In a field, with a size limit, a type list and help text
Drop a PNG or SVG here, or click to choose
One file, up to 1 MB.
<div class="field">
<label for="dz-logo">Store logo</label>
<div class="dropzone" data-max-size="1048576">
<input type="file" id="dz-logo" name="logo" accept=".png,.svg" aria-describedby="dz-logo-help">
<i class="icon icon-upload" aria-hidden="true"></i>
<span>Drop a PNG or SVG here, or click to choose</span>
</div>
<small id="dz-logo-help">One file, up to 1 MB.</small>
</div>
A tile beside existing images
<div class="stack" data-direction="row" data-gap="sm">
<img src="img/product-headphones.svg" alt="Headphones" width="96" height="96">
<img src="img/product-mug.svg" alt="Mug" width="96" height="96">
<div class="dropzone" data-size="tile">
<input type="file" accept="image/*" multiple aria-label="Add images">
<i class="icon icon-upload" aria-hidden="true"></i>
</div>
</div>
A preview of the chosen images, drawn by your own script
Drop images here or click to choose
<div class="stack" data-gap="sm" id="dz-preview">
<div class="dropzone">
<input type="file" accept="image/*" multiple aria-label="Choose images">
<i class="icon icon-upload" aria-hidden="true"></i>
<span>Drop images here or click to choose</span>
</div>
<div class="stack" data-direction="row" data-gap="sm" id="dz-preview-list"></div>
</div>
<script>
document.getElementById("dz-preview").addEventListener("change", (event) => {
const list = document.getElementById("dz-preview-list");
for (const img of list.querySelectorAll("img")) URL.revokeObjectURL(img.src);
list.replaceChildren(
...[...event.target.files].map((file) => {
const img = new Image(96, 96);
img.alt = file.name;
img.src = URL.createObjectURL(file);
return img;
}),
);
});
</script>
data-dragging="true" shows the drag-over state
Release to add the images
<div class="dropzone" data-dragging="true">
<input type="file" accept="image/*" multiple aria-label="Choose images">
<i class="icon icon-upload" aria-hidden="true"></i>
<span>Release to add the images</span>
</div>
Disabled, and invalid with an error message
Uploads are turned off
Drop a PNG or SVG here, or click to choose
Add a logo to continue.
<div class="stack">
<div class="dropzone">
<input type="file" accept="image/*" aria-label="Choose an image" disabled>
<i class="icon icon-upload" aria-hidden="true"></i>
<span>Uploads are turned off</span>
</div>
<div class="field" data-invalid>
<label for="dz-invalid">Store logo</label>
<div class="dropzone">
<input type="file" id="dz-invalid" accept=".png,.svg" aria-invalid="true" aria-describedby="dz-invalid-error">
<i class="icon icon-upload" aria-hidden="true"></i>
<span>Drop a PNG or SVG here, or click to choose</span>
</div>
<small id="dz-invalid-error" role="alert">Add a logo to continue.</small>
</div>
</div>
Reading change for accepted files and rejected for refused ones
PNG or JPG, up to 200 KB each
<div class="stack" data-gap="sm" id="dz-events">
<div class="dropzone" data-max-size="204800">
<input type="file" accept="image/png,.jpg" multiple aria-label="Choose images">
<i class="icon icon-upload" aria-hidden="true"></i>
<span>PNG or JPG, up to 200 KB each</span>
</div>
<output id="dz-events-result">Nothing chosen yet.</output>
</div>
<script>
const zone = document.getElementById("dz-events");
const result = document.getElementById("dz-events-result");
let accepted = "none";
let refused = "none";
const show = () => {
result.textContent = "Accepted: " + accepted + ". Rejected: " + refused + ".";
};
zone.addEventListener("change", (event) => {
accepted = [...event.target.files].map((file) => file.name).join(", ") || "none";
refused = "none";
show();
});
zone.addEventListener("rejected", (event) => {
refused = event.detail.rejected
.map(({ file, reason }) => file.name + " (" + reason + ")")
.join(", ");
show();
});
</script>
In a form, FormData.getAll returns the files
<form class="stack" data-gap="sm" id="dz-form">
<div class="dropzone">
<input type="file" name="photos" accept="image/*" multiple aria-label="Photos">
<i class="icon icon-upload" aria-hidden="true"></i>
<span>Drop photos here or click to choose</span>
</div>
<button type="submit" class="btn">Send</button>
<output id="dz-form-result">Nothing sent yet.</output>
</form>
<script>
document.getElementById("dz-form").addEventListener("submit", (event) => {
event.preventDefault();
const files = new FormData(event.currentTarget).getAll("photos");
document.getElementById("dz-form-result").textContent =
files.length + " file(s): " + files.map((file) => file.name).join(", ");
});
</script>
Events
The dropzone never uploads and draws no previews. Its form sends the files. Read them from the input.
| Event | On | Description |
|---|---|---|
input, change |
input[type=file], bubbles |
Fired after a pick or a drop.
event.target.files holds the accepted files
only.
|
rejected |
.dropzone, bubbles |
Fired when a dropped or picked file is refused.
event.detail.rejected is a list of
{ file, reason }, where reason is
"type" or "size". Refused files
are left off the input. A drop with no accepted file
leaves the files already on the input unchanged.
|
Keyboard
| Key | Description |
|---|---|
Tab |
Moves focus to the input. The box shows an accent focus ring. |
Enter, Space |
Opens the file picker. |
Accessibility
-
The input is the control: give it an
aria-label, or alabelwithforin a field. -
The upload icon is decoration: keep
aria-hidden="true"on it. A tile has no text, so itsaria-labelis the only name. - Link help text with
aria-describedby. -
Set
aria-invalid="true"on the input for the invalid look. In a.field[data-invalid]the error text shows; give itrole="alert". - Dragging is a convenience only. Everything also works by click and keyboard.
Attributes
| Attribute / element | Values | Description |
|---|---|---|
.dropzone |
The dashed box. It holds the input, an optional icon and a span of text. | |
data-size |
tile |
A 96px square with the icon only, to sit beside thumbnails. |
data-max-size |
bytes |
The largest file accepted, on .dropzone.
Larger files are rejected with reason size.
|
data-dragging |
true |
Set by the script while files are dragged over the box. Write it by hand to show the state. |
> input[type=file] |
name |
Covers the box. A click opens the picker and the form sends its files. |
accept |
.png, image/png,
image/*
|
Comma separated. Checked on dropped and picked files; a
mismatch is rejected with reason type.
|
multiple |
present | Allows several files. Without it only the first dropped file is taken. |
disabled |
present | Dims the box. It takes no files. |
aria-invalid |
true |
Draws the border in the danger colour. |
> .icon |
Decoration, e.g. icon-upload. Keep
aria-hidden="true".
|