The trick to styling a file input is that you

  • A) give the style you want to the input’s label
    not the input itself (except for hiding it)
  • and B) explicitly cause the label to delegate a click MouseEvent
    (rather than the default focus-on-click).

Styled File Input Demo

TL;DR (Short ’n’ Sweet)

  1. Hide the original file input by absolutely positioning it off screen:
    <input
        type="file"
        style="position: absolute; top: -10000px; left: -10000px"
    />
    
  2. Style the surrounding label however you like:
    <label
        style="
            border: 1px solid red;
            background-color: lightgray;
            padding: 1em;
            cursor: pointer;
            user-select: none;
        "
     >Upload File
     <input ... /></label>
    
  3. Cause the label to delegate to the input by its name:
    <label
        style="..."
        onclick="
            document.body
                .querySelector('[name=js-file-upload]')
                .dispatchEvent(
                    new MouseEvent('click')
                );
        "
     >Upload File
     <input ... /></label>
    

How does it work?

Normally an input will receive focus when you click on its label - as if you had clicked on the input itself.

<input type="file" />, however, is a special case. Rather than just focusing the cursor on the input, you actually want a file open dialog to appear. This requires delegation of the click, not just the focus.

Complete Code Example

In this example I’m letting the click event bubble to the form and then I catch and delegate it to the input.

<h1>Styled File-Input Demo</h1>
<form
  action=""
  onclick="
    document.body.querySelector('[name=js-file-upload]').dispatchEvent(
        new MouseEvent('click')
    );
  "
>
  <label
    for="js-file-upload"
    style="
      border: 1px solid red;
      padding: 1em;
      cursor: pointer;
      user-select: none;
    "
    >Upload File
    <input
      style="position: absolute; top: -10000px; left: -10000px"
      name="js-file-upload"
      type="file"
      multiple
    />
  </label>
</form>

Bad Ideas

You may encounter a lot of much fancier ways to do this, with much fancier explanations.

Those are, generally speaking, bad ideas.

Using label is the semantically correct thing to do - it’s how HTML was designed since the beginning.

It’s boring because it works (and has worked since the 90s), which is great!

This doesn’t require any special frameworks or plugins, and will work better both in browser support and for accessibility than doing the more complex things that go against the standard browser behavior.

References

See also: