The anatomy of a custom input[type="file"] component
Tags: javascript, html, css
Published: 2017-09-14 18:36:43

---
title: The anatomy of a custom input[type="file"] component
published: true
description: A breakdown of how they work for those that need a complex one
tags: javascript, html, css
---

I had to develop a custom upload modal.  So I bit the bullet, Googled "form upload file" and there it is, there's an `<input type="file" />`, excellent I thought.  I used it and here's what I was greeted with:

![Basic \<input type="file" />](https://thepracticaldev.s3.amazonaws.com/i/pappjgryf9rkehtbbjl9.png)

Way off from the provided design.

So, where to start? Well actually I read [a blog post](https://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/), which led me to [a demo](https://tympanus.net/Tutorials/CustomFileInputs/).  With those two sources I was able to connect the dots and figure out the approach.

## The basic problem

The basic problem is a simple one, but not straightforward to fix if you don't know how.  The problem is that the input has pre-set text, layout and all that shebang.  I can't format that stuff to follow the designs, I don't have any selectors to work with. And even if I did I'd have to do black magic to get it to look how I want it.

But thankfully it's not too problematic to solve if you know how to.

## The basic solution (CSS)

Simply, hide the `<input type="file" />`, and render your own version of it from the ground up, with the magic of the `<label>` tag.

### Hide the non-ideal default

So let's start by hiding it, this can be done simply by the following styles:

```css
input[type="file"] {
  opacity: 0;
  position: absolute;
  pointer-events: none;
  // alternative to pointer-events, compatible with all browsers, just make it impossible to find
  width: 1px;
  height: 1px;
}
```

With those two lines you've made it invisible (not un-existent like you would with `display: none;`) and given it `position: absolute;` so it doesn't interfere with the document flow and `pointer-events: none;` so clicking on other visible elements doesn't trigger this one (because it is technically still there, just not visible).

### Being able to click it again

So, if our element is invisible, how can we click it? That's where the magic of `label` comes in.  Such useful things.

Our HTML would now look like so:

```html
<input type="file" id="myuniqueid" />
<label for="myuniqueid">!!Upload file!!</label>
```

This allows us to click on `!!Upload file!!` and trigger the upload file dialog from the browser.  That is the magic of labels.

So good, then all we need to do is style this label to look like a button or whatever we want.  Simple enough.

```css
input[type="file"] + label {
  // your styles here
}
```

## The basic solution (JS)

So now we have the button looking like we want.  Now we want it working like we want.

In all of the following steps we'll have the following to handle file changes (JSX syntax is assumed):

```js
<input type="file" id="myuniqueid" onChange={handleChange} />
```

Where `handleChange` is our function, which we'll be exploring.  `onChange` is triggered after the user has uploaded one or more files or has cancelled their interaction.

### If user cancels upload

We need to be able to handle if the user cancels their upload interaction, this can be done simply:

```js
function handleChange(event) {
  // User cancelled
  if (!event.target.files[0]) {
    return
  }
}
```

### Getting the file's name

To get the file's name one does `event.target.files[0].name`.

### Previewing the file

Maybe we want to preview the file in our file upload element.  In this case there are a couple approaches, one is to upload to Amazon S3 and then show the uploaded image, another one (which we'll do now) is to do a local preview, before an upload has taken place.

So here we go, our local previewer:

```js
function generatePreviewImgUrl(file, callback) {
  const reader = new FileReader()
  const url = reader.readAsDataURL(file)
  reader.onloadend = e => callback(reader.result)
}
```

So now our `handleChange` function looks like this:

```js
function handleChange(event) {
  const file = event.target.files[0]

  // User cancelled
  if (!file) {
    return
  }

  generatePreviewImgUrl(file, previewImgUrl => {
    // (assuming we use React)
    this.setState({ previewImgUrl })
  })
}
```

Where later in the React component you do:

```js
<img src={this.state.previewImgUrl} />
```

## In closing

Hope this guide was useful.  I tried to put together the core concepts in an easy reference-able guide so that you can re-use it in any environment you need. :)

The concepts are not difficult, you just need to know that one key point:

- Using hidden input + styled label for the button