Tutorial

Simple Custom File Selector with Vue.js

Published on April 20, 2017
author

Joshua Bemenderfer

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>

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
Joshua Bemenderfer

author

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.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


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!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.