Join the AI Workshop to learn more about AI and how it can be applied to web development. Next cohort February 1st, 2026
The AI-first Web Development BOOTCAMP cohort starts February 24th, 2026. 10 weeks of intensive training and hands-on projects.
The Drag and Drop API allows users to drag files directly onto an element to upload them.
Basic setup
Create a drop zone element with drag event handlers:
<div
id="dropzone"
ondragover="dragOverHandler(event)"
ondragleave="dragLeaveHandler(event)"
ondrop="dropHandler(event)"
>
Drop files here
</div>
Handling drag events
Use ondragover to style the element when a file is being dragged over it, and ondragleave to reset:
function dragOverHandler(event) {
event.preventDefault()
const dropzone = document.querySelector('#dropzone')
dropzone.classList.add('dragging_over')
}
function dragLeaveHandler(event) {
event.preventDefault()
const dropzone = document.querySelector('#dropzone')
dropzone.classList.remove('dragging_over')
}
#dropzone.dragging_over {
border: 2px dashed #fff;
background-color: #666;
}
Handling the drop
The ondrop event fires when files are dropped. Access the files through event.dataTransfer.items:
async function dropHandler(event) {
event.preventDefault()
const endpoint = '/api/upload'
if (event.dataTransfer.items) {
const formData = new FormData()
for (let item of event.dataTransfer.items) {
if (item.kind === 'file') {
const file = item.getAsFile()
if (file) {
if (!file.type.match('image.*')) {
alert('only images supported')
return
}
formData.append('files', file)
}
}
}
try {
const response = await fetch(endpoint, {
method: 'POST',
body: formData,
})
if (response.ok) {
console.log('File upload successful')
} else {
console.error('File upload failed', response)
}
} catch (error) {
console.error('Error uploading file', error)
}
}
}
Using Alpine.js
Alpine.js simplifies the drag and drop implementation:
<div
x-data="{ dragover: false }"
:class="{ '*:cursor-alias': dragover }"
@dragover.prevent="dragover = true"
@dragleave.prevent="dragover = false"
@drop.prevent="drop"
>
Drop files here
</div>
The .prevent modifier automatically calls preventDefault() to stop the browser from opening the file.
Define the drop function:
async function drop(event) {
if (!event.dataTransfer.items) return
const formData = new FormData()
for (let item of event.dataTransfer.items) {
if (item.kind === 'file') {
const file = item.getAsFile()
if (file) {
if (!file.type.match('image.*')) {
alert('only images supported')
return
}
formData.append('files', file)
}
}
}
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
if (response.ok) {
console.log('File upload successful')
}
}