DEV Community

Eduardo Lavaque
Eduardo Lavaque

Posted on • Edited on

The anatomy of a custom input[type="file"] component

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="/>

Way off from the provided design.

So, where to start? Well actually I read a blog post, which led me to a demo. 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:

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;
}
Enter fullscreen mode Exit fullscreen mode

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:

<input type="file" id="myuniqueid" />
<label for="myuniqueid">!!Upload file!!</label>
Enter fullscreen mode Exit fullscreen mode

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.

input[type="file"] + label {
  // your styles here
}
Enter fullscreen mode Exit fullscreen mode

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):

<input type="file" id="myuniqueid" onChange={handleChange} />
Enter fullscreen mode Exit fullscreen mode

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:

function handleChange(event) {
  // User cancelled
  if (!event.target.files[0]) {
    return
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

function generatePreviewImgUrl(file, callback) {
  const reader = new FileReader()
  const url = reader.readAsDataURL(file)
  reader.onloadend = e => callback(reader.result)
}
Enter fullscreen mode Exit fullscreen mode

So now our handleChange function looks like this:

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

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

  generatePreviewImgUrl(file, previewImgUrl => {
    // (assuming we use React)
    this.setState({ previewImgUrl })
  })
}
Enter fullscreen mode Exit fullscreen mode

Where later in the React component you do:

<img src={this.state.previewImgUrl} />
Enter fullscreen mode Exit fullscreen mode

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

Top comments (6)

Collapse
 
pushp1992 profile image
Pushp Singh

Dude, no where you have mentioned where to write this code chunk,

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

Even, you have nowhere initialised const url in your code.

After testing your code i am getting below data instead of image:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAABHNCSVQICAgIfAhkiAAAIABJREFUeJzsvXmYJEd1L/o7kUttXb33rJpNmkWjBbRcsRgJsQhJgC0BMsZgVj8D17425l4bf9d+z2Bh8PUzXvjs62ewsD/L1/Y1zyDggQVoQUhCIMBakIQYIYkRGs2MZnp6equ9KiPeH1lZHZ2dS2RWVnVVT/74millRpw4cfLEiXNOREYCKVKkSJEiRYoUKVKkSJEiRYoUKVIMNmi9GUgxXDh9+vSOTCazSQgxyRjLWpaVISJT0zSDiEzOualpmgkAlmU1GGMNIUTDsqymEKKhaVqdc14jotOLi4uz27Zte3a9+5RieJAarBQAQNVqdSfnfFLTtAnLss4RQuwjoj1EtJWIJoUQOgBdCGEyxkwhRIaINCGEBkADoBERY4xB13UAQKvVAuccQggOwAJgEZElhLCIqM45bxBRA

Your code seems to be buggy. please fix it and it will be great if you can attach codepen with your code.

Collapse
 
jsherz profile image
James Sherwood-Jones

If you're looking for how to structure your code, the Mozilla Developer Network has some great articles on starting web development and JavaScript: developer.mozilla.org/en-US/docs/L...

Collapse
 
greduan profile image
Eduardo Lavaque

Hey sorry for the slow response.

That function doesn't need a particular location. It just needs to be available to the code that needs it. So it's up to you where to put it.

As I recall, that output from the function is the correct output. It encodes the image into text and tells the img tag to load it from there when you put it in its src tag.

Collapse
 
pichuperez profile image
Gabriela Perez G

Agree with @Pushup Singh this is incomplete as it doesnt handle the correct image url to render the preview.

Collapse
 
tomhodgins profile image
Tommy Hodgins

Nice! This approach is very similar to how I create file upload buttons, though I don't use React. You can have fun outputting the filename of the selected file to the page as well - check out my demo here: codepen.io/tomhodgins/pen/PbMOar

Collapse
 
ashleyjsheridan profile image
Ashley Sheridan

I do a similar thing, I wrote about it in an article where I outlined how to style other form elements too while keeping them accessible: ashleysheridan.co.uk/blog/Accessib...