How to customise a file upload control

If your website lets users upload files, you have probably compared the default file upload control with custom solutions. In this article I will show you can have both, using a method that uses the default input, but looks completely custom.

The default file upload

This is a default file upload:

<input type="file" id="file-upload" />
<label for="file-upload">Upload a file</label>

It is one of those HTML elements that has a lot of usability and accessibility built in.

To name a few things that you get for free when using the native <input type=file>:

  • it can show the file name of the upload file
  • it can be used with just a keyboard
  • on phones, it will let your user take a photo or choose to select files from specific apps
  • on desktop browsers, it will let your user browse the file system
  • it can be set to allow multiple files to be selected
  • it can distinguish between file types with its optional accept attribute
  • it exposes a useful files DOM attribute for usage in your scripts (as well as a slightly less useful value)
  • it integrates with your browser’s built in error mechanism

File input that matches your brand

Something you probably don’t get for free is a smooth integration with your brand’s design guidelines. They might prescribe buttons to be blue or rounded corners everywhere.

The HTML example above related the input with a corresponding label. I used corresponding for and id attributes, but wrapping the input inside a label works just as well.

In all browsers, you will find that you don’t actually have to click the input to activate it: clicking the label will do. Because of this, the trick, which I learned from Krijn, is simple: hide the <input/> and style the <label>.

Hide the input

You want to hide the input, but do that in a way that screenreaders don’t assume it does not exist. The method for hiding is to make it ‘visually’ hidden, but not actually hidden to assistive technologies.

This is one way:

input[type="file"] { 
  opacity: 0; /* make transparent */
  z-index: -1; /* move under anything else */
  position: absolute; /* don't let it take up space */

There are various different approaches to this, the important thing is that the method does not render the input unusable (as visibility: hidden would, for example), or move it off screen (as left: -9999em would), as that would move any native error messages off screen as well.

Style the label

Now, only the label is visible and you can style it however you like. You can make it look like a button, add background gradients, drop shadows, whatever your heart desires.

Add focus styles

It would be good to add the focus styles that your input had to the label. You can select this in CSS as follows:

input[type="file"]:focus + label {
  outline: 2px solid;  /* example focus style */


To make a custom file upload control, use a standard file upload and label, then visually hide the input and style the label.

Update: On Twitter, Phil pointed out that if you use the above example, users can’t see the file name of what they’ve uploaded. This is a great point; in my own implementation I’ve used some JavaScript to access the input’s files property onchange of the input, which I then inserted into the page if it had changed (I’ve created a Codepen to show how to do this)

Leave a comment

Hidde de Vries (@hdv on Twitter) is a freelance front-end developer based in Rotterdam, The Netherlands. He helps governments and businesses get usable front-ends for their end users. Member of Fronteers and former volunteer there, organising conferences, meetups and workshops.