File select elements are easily one of the ugliest input types on the web. They’re implemented differently in every browser and are generally incredibly ugly. There are some workarounds though, and we’ll show you one approach here using labels and a bit of Vue.js magic.
If you don’t have a project set up already, initiate a new one with vue-cli’s webpack-simple template.
$ npm install -g vue-cli
$ vue init webpack-simple ./file-upload # Follow the prompts.
$ cd ./file-upload
$ npm install # or yarn
The goal of this component is simply to wrap a hidden input type=“file” element in a label, and display something else inside. This technique, while simple, is surprisingly effective.
<template>
<!--
Everything is wrapped in a label, which acts as a clickable wrapper around a form element.
In this case, the file input.
-->
<label class="file-select">
<!-- We can't use a normal button element here, as it would become the target of the label. -->
<div class="select-button">
<!-- Display the filename if a file has been selected. -->
<span v-if="value">Selected File: {{value.name}}</span>
<span v-else>Select File</span>
</div>
<!-- Now, the file input that we hide. -->
<input type="file" @change="handleFileChange"/>
</label>
</template>
...
And now, some nice simple styles to give it a rather button-y look.
...
<style scoped>
.file-select > .select-button {
padding: 1rem;
color: white;
background-color: #2EA169;
border-radius: .3rem;
text-align: center;
font-weight: bold;
}
/* Don't forget to hide the original file input! */
.file-select > input[type="file"] {
display: none;
}
</style>
Files are a very special type to the browser, so there are a few special rules that make them a bit tricky to work with at times. (More on that here.) Despite this, we can actually get away with a very simple controller. It’s as short as any other custom input element.
<script>
export default {
props: {
// Using value here allows us to be v-model compatible.
value: File
},
methods: {
handleFileChange(e) {
// Whenever the file changes, emit the 'input' event with the file data.
this.$emit('input', e.target.files[0])
}
}
}
</script>
Now, we can import our new component in our app and use it like any other, with full v-model support.
<template>
<div>
<p>My File Selector: <file-select v-model="file"></file-select></p>
<p v-if="file">{{file.name}}</p>
</div>
</template>
<script>
import FileSelect from './FileSelect.vue'
export default {
components: {
FileSelect
},
data() {
return {
file: null
}
}
}
</script>
Now you have full reactive access to files your users select through the component. You could then wrap it in an upload component, or process the data in some way with the FileReader API. Have fun!
For those of you who want everything at once, here you go! As promised, only 41 lines long. :)
<template>
<label class="file-select">
<div class="select-button">
<span v-if="value">Selected File: {{value.name}}</span>
<span v-else>Select File</span>
</div>
<input type="file" @change="handleFileChange"/>
</label>
</template>
<script>
export default {
props: {
value: File
},
methods: {
handleFileChange(e) {
this.$emit('input', e.target.files[0])
}
}
}
</script>
<style scoped>
.file-select > .select-button {
padding: 1rem;
color: white;
background-color: #2EA169;
border-radius: .3rem;
text-align: center;
font-weight: bold;
}
.file-select > input[type="file"] {
display: none;
}
</style>
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!