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

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

Daniel Kelly
Daniel Kelly
Updated: April 1st 2025
This entry is part 1 of 1 in 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:

  1. Allow the user to select one or multiple files
  2. Display each file name below an upload button
  3. Allow a file to be deleted from the list
  4. Emit a changed event with an array of the selected File objects whenever the user adds or removes a file.

If you want to learn about developing a more robust file upload component including features like:

  1. Adding client side validation
  2. Displaying file previews
  3. 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.

Default file input style isn’t so pretty

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 />
button-upload.gif

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>
list-files.gif

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>
delete-files.gif

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!

Related Courses

Start learning Vue.js for free

Daniel Kelly
Daniel Kelly
Daniel is the lead instructor at Vue School and enjoys helping other developers reach their full potential. He has 10+ years of developer experience using technologies including Vue.js, Nuxt.js, and Laravel.

Comments

Latest Vue School Articles

Give Your Web Apps a Voice with Eleven Labs AI

Give Your Web Apps a Voice with Eleven Labs AI

Bring your Nuxt Content pages to life with Nuxt Content Narrator, a seamless integration of Eleven Labs’ text-to-speech technology. Easily convert Markdown content into high-quality, natural-sounding audio with synchronized text highlighting and a customizable player.
Daniel Kelly
Daniel Kelly
What is a Vue.js Error Boundary Component?

What is a Vue.js Error Boundary Component?

Error boundaries in Vue are a game-changing tool that prevents your entire application from crashing when a component fails, ensuring a smooth user experience. In this guide, we'll explore how error boundaries work, how to implement them in your Vue applications, and where to find pre-built solutions to save you time.
Daniel Kelly
Daniel Kelly

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!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.