// Tutorial //

Simple Custom File Selector with Vue.js

Published on April 20, 2017
Default avatar
By Joshua Bemenderfer
Developer and author at DigitalOcean.
Simple Custom File Selector with Vue.js

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.

Setup

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

Component Template & Styles

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.

FileSelect.vue (template)
<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.

FileSelect.vue (styles)
...
<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>

The Logic

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.

FileSelect.vue (script)
<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>

Usage

Now, we can import our new component in our app and use it like any other, with full v-model support.

App.vue
<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!

Complete Component Code

For those of you who want everything at once, here you go! As promised, only 41 lines long. :)

FileSelect.vue
<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>

Want to learn more? Join the DigitalOcean Community!

Join our DigitalOcean community of over a million developers for free! Get help and share knowledge in our Questions & Answers section, find tutorials and tools that will help you grow as a developer and scale your project or business, and subscribe to topics of interest.

Sign up
About the authors
Default avatar
Developer and author at DigitalOcean.

Still looking for an answer?

Was this helpful?
Leave a comment