Building a vue.js drag-and-drop file component
I was recently tasked with a drag-and-drop file upload component for a guest-entries form. In this post we'll explore how to build just that with vue.js.
(scroll down to see a live demo)

Creating the markup
This is the easy part (really!). The main principle we'll be using is: hide our file input field (visually only, don't actually display: none;
it) and wrap our drag-and-drop zone inside a label field.
We're using Tailwind to create a simple HTML component. I've already included the necessary vue attributes:
<div class="flex items-center justify-center w-full h-screen text-center" id="app">
<div class="p-12 bg-gray-100 border border-gray-300" @dragover="dragover" @dragleave="dragleave" @drop="drop">
<input type="file" multiple name="fields[assetsFieldHandle][]" id="assetsFieldHandle"
class="w-px h-px opacity-0 overflow-hidden absolute" @change="onChange" ref="file" accept=".pdf,.jpg,.jpeg,.png" />
<label for="assetsFieldHandle" class="block cursor-pointer">
<div>
Explain to our users they can drop files in here
or <span class="underline">click here</span> to upload their files
</div>
</label>
<ul class="mt-4" v-if="this.filelist.length" v-cloak>
<li class="text-sm p-1" v-for="file in filelist">
${ file.name }<button type="button" @click="remove(filelist.indexOf(file))" title="Remove file">x</button>
</li>
</ul>
</div>
</div>
Now if you would run this, these couple of lines already work! But some things are missing:
- We can't see which files are selected (because we hid the input element).
- Since we can't see the files, we also can't remove them
- Our area as a whole is clickable, but we still have to support drag-and-drop
Vue is a great framework to simplify all this functionality. Let's have a look at the code:
new Vue({
el: '#app',
delimiters: ['${', '}'], // Avoid Twig conflicts
data: {
filelist: [] // Store our uploaded files
},
methods: {
onChange() {
this.filelist = [...this.$refs.file.files];
},
remove(i) {
this.filelist.splice(i, 1);
},
dragover(event) {
event.preventDefault();
// Add some visual fluff to show the user can drop its files
if (!event.currentTarget.classList.contains('bg-green-300')) {
event.currentTarget.classList.remove('bg-gray-100');
event.currentTarget.classList.add('bg-green-300');
}
},
dragleave(event) {
// Clean up
event.currentTarget.classList.add('bg-gray-100');
event.currentTarget.classList.remove('bg-green-300');
},
drop(event) {
event.preventDefault();
this.$refs.file.files = event.dataTransfer.files;
this.onChange(); // Trigger the onChange event manually
// Clean up
event.currentTarget.classList.add('bg-gray-100');
event.currentTarget.classList.remove('bg-green-300');
}
}
});
In just a couple of lines we've added all the necessary ux features for a drag-and-drop file component.
Craft Tips
A couple of things are worth mentioning if you are planning to use this in a Craft guest-entries setup.
- Make sure you add
enctype="multipart/form-data"
to your form. - I would strongly suggest you check the
maxUploadFileSize
setting, and build in some validation to prevent massive file uploads from your users.
This setup only starts uploading when you hit the submit button. In some cases this would be disadvantageous. But I would argue that in most cases, this is actually a good thing, as it prevents unnecessary file uploads on your server for users that decide not to submit the form.