Build a File Upload Component in Vue.js with the Composition API

Part of the series: File Upload in Vue.js
In this tutorial, we’ll build a simple file upload component in Vue.js using the Composition API. It will:
- Allow the user to select one or multiple files
- Display each file name below an upload button
- Allow a file to be deleted from the list
- Emit a
changedevent with an array of the selectedFileobjects whenever the user adds or removes a file.
If you want to learn about developing a more robust file upload component including features like:
- Adding client side validation
- Displaying file previews
- And supporting drag and drop functionality
Then checkout our comprehensive course File Uploads in Vue.js.
Let’s Get Started With an HTML File Input
The first step in creating any file upload component is to create an HTML file input element.
<!-- FileInput.vue -->
<script setup lang="ts"></script>
<template>
<input type="file" />
</template>It can optionally accept multiple files with the multiple attribute.
<input type="file" multiple />Or we can make multiple a prop to give control to the consuming component.
<!-- FileInput.vue -->
<script setup lang="ts">
const props = defineProps<{
multiple?: boolean;
}>();
</script>
<template>
<input type="file" :multiple="multiple" />
</template>Use a Button Styled Label to Trigger the File Input
Out of the box, the file input is not very pretty and it’s not very easily styled. We can work around this by taking advantage of the default behavior of labels.

When a label is clicked, the browser will focus the file input. So let’s hide the ugly file input and use a label styled as a button to trigger the file selection.
<label for="file-input" class="btn">
Upload File
</label>
<input id="file-input" type="file" hidden />
Capture Files on the Input Change Event
In order to capture the files that the user selects, we can listen for the change event on the file input.
<input type="file" multiple @change="handleFileSelect" />Selected files are available on the files property of the event target.
function handleFileSelect(e: Event) {
const input = e.target as HTMLInputElement;
const filesAsArray = Array.from(input?.files || []);
}Store the Selected Files in a Reactive Array (a Vue.js Ref)
To render the selected files in the UI and emit the selected files when the user adds or removes a file, we can store the files in a reactive array (a Vue.js ref).
const files = ref<File[]>([]);That means we should push the selected files to the files in the handleFileSelect function.
function handleFileSelect(e: Event) {
const input = e.target as HTMLInputElement;
const filesAsArray = Array.from(input?.files || []);
files.value = files.value.concat(filesAsArray);
}Render the Selected Files in the UI
To render the selected files in the UI, we can simply loop over the files ref and render a list item for each file.
<template>
<ul>
<li v-for="file in files" :key="file.name">
{{ file.name }}
</li>
</ul>
</template>
Emit the Selected Files
To inform the consuming component when a file is added or removed, we can use the emit function. First we define the event name and type.
const emit = defineEmits<{
(e: "changed", files: File[]): void;
}>();Then we watch the files ref and emit the selected files.
watch(files, (newFiles) => {
emit("changed", newFiles);
});Remove a File from the List
To remove a file from the list, we can use the splice method on the files ref.
function removeFile(index: number) {
files.value.splice(index, 1);
}Of course, we should also add some UI elements to the template to allow the user to remove a file.
<template>
<ul>
<li v-for="(file, index) in files" :key="file.name">
{{ file.name }}
<button @click="removeFile(index)">Remove</button>
</li>
</ul>
</template>
The Complete Component
Now that we have all the pieces, we can put them together to create the complete component.
<template>
<div>
<label for="file-input" class="btn">Upload File</label>
<input
id="file-input"
type="file"
:multiple="multiple"
@change="handleFileSelect"
hidden
/>
<ul>
<li v-for="(file, index) in files" :key="file.name">
{{ file.name }} <button @click="removeFile(index)">Remove</button>
</li>
</ul>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
multiple?: boolean;
}>();
const files = ref<File[]>([]);
const emit = defineEmits<{
(e: "changed", files: File[]): void;
}>();
function handleFileSelect(e: Event) {
const input = e.target as HTMLInputElement;
const filesAsArray = Array.from(input?.files || []);
files.value = files.value.concat(filesAsArray);
}
function removeFile(index: number) {
files.value.splice(index, 1);
}
</script>Conclusion
This simple yet functional file upload component demonstrates the power and simplicity of Vue.js with the Composition API. It provides a solid foundation that you can build upon based on your specific needs. Style it however you’d like! With Tailwind CSS, it’s easy to make it look great.
Add more advanced features like:
- file previews
- drag and drop
- validation
- and hook it up to a backend API
in our comprehensive course File Uploads in Vue.js! Don’t miss it!
Start learning Vue.js for free

Comments
Latest Vue School Articles
5 Component Design Patterns to Boost Your Vue.js Applications

Vibe Coding a Collaborative Editor with Comment Support with Nuxt UI and Jazz

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.


