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

greduan profile image Eduardo Lavaque Updated on ・3 min read

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

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;

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>

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

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} />

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]) {

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)

So now our handleChange function looks like this:

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

  // User cancelled
  if (!file) {

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

Where later in the React component you do:

<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


Editor guide
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:


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

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

greduan profile image
Eduardo Lavaque Author

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.

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.

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

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