Styling File Inputs

Written by
Published
Updated
Typical Read
9 minutes

Google "styling file inputs" and you’ll find dozens of techniques on how to theme file inputs. The most optimized, semantic and accessible way to style <input type="file"> is just with a little CSS and <label>.

tl;dr

Styling file inputs using CSS and the label technique allows you to customize the look and feel. This technique conforms to semantics, is accessible, and cross-browser compliant. Check out the code and example below.

CSS

[type="file"] {
  border: 0;
  clip: rect(0, 0, 0, 0);
  height: 1px;
  overflow: hidden;
  padding: 0;
  position: absolute !important;
  white-space: nowrap;
  width: 1px;
}
[type="file"] + label {
  /* File upload button styles */
}
[type="file"]:focus + label,
[type="file"] + label:hover {
  /* File upload hover state button styles */
}
[type="file"]:focus + label {
  /* File upload focus state button styles */
}

HTML

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

Or if you prefer Sass — and who doesn’t:

SCSS

[type="file"] {
  border: 0;
  clip: rect(0, 0, 0, 0);
  height: 1px;
  overflow: hidden;
  padding: 0;
  position: absolute !important;
  white-space: nowrap;
  width: 1px;
  + label {
    // File upload button styles
  }
  &:focus + label,
  + label:hover {
    // File upload hover state button styles
  }
  &:focus + label {
    // File upload focus state button styles
  }
}

Styling File Inputs Demo

See the Pen Beautiful CSS-Only File Inputs by Ben Marshall (@bmarshall511) on CodePen.

Styling File Inputs Guide

I’ve spent countless hours styling file inputs using a number of different techniques out there. The end result is always the same — frustration. A common (IMO worst) technique to style file inputs is faking buttons with extra, non-semantic HTML, CSS, and JS. Dirty, dirty, dirty, not to mention the drawbacks for usability and touch. Don’t fret! There is a better way.

The Problem

Trying and failing to style a <input type="file" /> control is a twisted rite of passage in the web dev world. There’s a (very) long thread on Stack Overflow dedicated to it. The problem is every browser has a different way of implementing <input type="file" /> and few can be styled with CSS.

Styling File Inputs

The label technique is a great solution to style file inputs since pressing the <label> triggers the focus event for the bound input. Since we’re dealing with file inputs, it works out as a click event. This results in the file browser pop-up and is a great semantic solution! No JavaScript, no other complex solutions like cursor position tracking, and just these two lines.

CSS File Input label Technique

Styling clean, semantic and accessible upload buttons require two things: CSS & label. I’ll go over how and demonstrate how a little extra JS (optional) can enhance the UX. The input file CSS possibilities are limitless once you understand how this technique works!

First, create the semantic HTML.

You’ll start with the HTML semantic for a file input. It must have a label preceding it, this will become the upload button:

HTML

<input type="file" id="file" />
<label for="file">choose a file</label>

Next, hide the native button.

Bet you’re thinking display: none or visibility: hidden? Nope. Those won’t work because the input value won’t be sent to the server when submitted. It’ll also be excluded out of the tab order making it no longer accessible. In order to keep it accessible, use the following CSS:

CSS

[type="file"] {
  border: 0;
  clip: rect(0, 0, 0, 0);
  height: 1px;
  overflow: hidden;
  padding: 0;
  position: absolute !important;
  white-space: nowrap;
  width: 1px;
}

Why 1x1px? Setting property values to zero ends up throwing the element out of tab order in some browsers. position: absolute guarantees the element does not interfere with the sibling elements. Don’t forget when styling file inputs, accessibility is an important factor.

The fun part, styling the upload button.

Now we’ll style the file input by using the <label> element as the upload button. From there, use your creative CSS juices on it! Check out the basic example below:

CSS

[type="file"] + label {
  background-color: #000;
  border-radius: 4rem;
  color: #fff;
  cursor: pointer;
  display: inline-block;
  font-family: 'Poppins', sans-serif;
  font-size: 1rem;
  font-weight: 700;
  height: 4rem;
  line-height: 4rem;
  padding-left: 2rem;
  padding-right: 2rem;
  transition: background-color 0.3s;
}
[type="file"]:focus + label,
[type="file"] + label:hover {
    background-color: #f15d22;
}
[type="file"]:focus + label {
  outline: 1px dotted #000;
  outline: -webkit-focus-ring-color auto 5px;
}

The complete code.

Let’s put it all together, here’s the complete code below:

CSS

[type="file"] {
  border: 0;
  clip: rect(0, 0, 0, 0);
  height: 1px;
  overflow: hidden;
  padding: 0;
  position: absolute !important;
  white-space: nowrap;
  width: 1px;
}
[type="file"] + label {
  background-color: #000;
  border-radius: 4rem;
  color: #fff;
  cursor: pointer;
  display: inline-block;
  padding-left: 2rem 4rem;
}
[type="file"]:focus + label,
[type="file"] + label:hover {
    background-color: #f15d22;
}
[type="file"]:focus + label {
  outline: 1px dotted #000;
}

It’s that easy! You can now adjust the styling as needed to create your own upload button to match your site styles.

The accessible part.

How do you know that an element on the website is accessible?

  1. Elements should communicate a feeling that you can tap or click on it.
  2. The cursor icon should change to an appropriate one when hovering the element.
  3. Tabbed keyboard navigation should be intuitive.

The label technique covers all of these requirements.

More about keyboard navigation.

If users are unable to navigate your website using just a keyboard, you are doing something wrong. Hiding the input correctly and indicating when the element is focused (i.e. rendering [type="file"]:focus on the label). ensures this functionality stays intact.

-webkit-focus-ring-color: auto 5px is a little trick for obtaining default outline looks on Chrome, Opera and Safari.

Touch Issues with FastClick

In case you’ve been using FastClick (a library for eliminating the 300ms tap-pause on touch-capable devices) and have plans to add some extra markup to the content of a label, the button won’t work as it should, unless you use pointer-events: none, respectively:

[type="file"] + label * {
  pointer-events: none;
}

Enhance the UX with JavaScript

When styling file inputs, it’s important to provide users with feedback when a file is selected for upload. When hiding the native file input, users don’t have a way to tell a file was selected.

There’s a simple fix with some JavaScript that’ll provide this functionality:

  1. When the user selects a file, the text of a label will become the name of the selected file.
  2. If there were multiple files selected, the text will tell us how many of them were selected.

Check out the example below:

HTML

<input type="file" name="file" id="file" data-multiple-caption="{count} files selected" multiple />

JS

var inputs = document.querySelectorAll( '.inputfile' );
Array.prototype.forEach.call( inputs, function( input ) {
  var label = input.nextElementSibling,
              labelVal = label.innerHTML;
  input.addEventListener( 'change', function( e ) {
    var fileName = '';
    if ( this.files && this.files.length > 1 ) {
      fileName = ( this.getAttribute( 'data-multiple-caption' ) || '' ).replace( '{count}', this.files.length );
    } else {
      fileName = e.target.value.split( '\\' ).pop();
    }
    if ( fileName ) {
      label.querySelector( 'span' ).innerHTML = fileName;
    } else {
      label.innerHTML = labelVal;
    }
  });
});

Having the native [multiple] attribute allows users to select more than one file per upload. Whereas [data-multiple-caption] is a fictive attribute for expressing the message if multiple files were selected. Here you can set a custom message. The use of the {count} phrase is optional and the fragment is replaced with the number of files selected.

An interesting thing is that you can unset a value of the input by pressing the ESC button while in the file browser. This is possible only in Chrome and Opera. Therefore, we use labelVal for storing the default value of the label and bringing it back when necessary.

[multiple] is not supported in IE 9 and below; neither is the files property of JavaScript. For the latter case, we simply rely on value. Since it usually has a value of C:\fakepath\filename.jpg format, the split( '\\' ).pop() extracts what’s actual — the name of the file.

There is also a jQuery version of this code you can download here.

No JavaScript, no problem!

Since there is no JavaScript-less way to indicate if any files were selected, it would be better to rely on the default looks of the file input for the sake of usability.

Styling file inputs with an enhanced UX is a cinch. All we need to do is to add a .no-js class name to the <html> element. We’ll use JavaScript to remove it if available — that’s how we’ll determine if our JS enhancement will work.

Many frameworks like Foundation and Bootstrap already do this.

<html class="no-js">
  <head>
    <!-- remove this if you use Modernizr -->
    <script>(function(e,t,n){var r=e.querySelectorAll("html")[0];r.className=r.className.replace(/(^|\s)no-js(\s|$)/,"$1js$2")})(document,window,0);</script>
  </head>
</html>
.js [type="file"] {
  border: 0;
  clip: rect(0, 0, 0, 0);
  height: 1px;
  overflow: hidden;
  padding: 0;
  position: absolute !important;
  white-space: nowrap;
  width: 1px;
}
.no-js [type="file"] + label {
  display: none;
}

Firefox Bug Fix

There’s a known Firefox bug when it comes to styling file inputs that completely ignores the input[type="file"]:focus expression, yet :hover and :active work just fine — don’t ask me why. However, Firefox allows us to catch the focus event using JavaScript. This workaround adds a class to the file input element letting us control the focus style:

JS

input.addEventListener( 'focus', function(){ input.classList.add( 'has-focus' ); });
input.addEventListener( 'blur', function(){ input.classList.remove( 'has-focus' ); });

CSS

[type="file"]:focus + label,
[type="file"].has-focus + label {
  outline: 1px dotted #000;
  outline: -webkit-focus-ring-color auto 5px;
}

Using a CSS framework?

Many popular CSS frameworks like Foundation and Bootstrap already have a way to customize upload buttons. Check out the examples below.

Styling File Inputs with Foundation

Customizing the file upload button in Foundation uses the same technique above. To hide the native upload button it uses the visibility class for screen readers (.show-for-sr).

Use Foundation’s settings file to update variables to customize the button to your liking. The HTML structure follows the same pattern shown above:

<label for="file" class="button">Upload File</label><input type="file" id="file" class="show-for-sr" />

Styling File Inputs with Bootstrap

Bootstrap’s method for styling file inputs isn’t as clean or optimized as this technique or Foundations’ — just another reason I prefer Foundation over Bootstrap. It requires additional JavaScript in order for it to override the native upload button.

<div class="custom-file">
  <input type="file" class="custom-file-input" id="file">
  <label class="custom-file-label" for="file">Choose file</label>
</div>

This method may not be as clean, but it does provide a little more flexibility for translations:

$custom-file-text: (
  en: "Browse",
  es: "Elegir"
);
<div class="custom-file">
  <input type="file" class="custom-file-input" id="file" lang="es">
  <label class="custom-file-label" for="file">Seleccionar Archivo</label>
</div>

The :lang() pseudo-class is used to allow for translation of the “Browse” text into other languages. Override or add entries to the $custom-file-text Sass variable with the relevant language tag and localized strings. English strings can be customized the same way.

Styling File Input Alternatives

Like with most things in web development, there’s more than one way to skin a cat. Here’s some other popular techniques to style file inputs:

In Conclusion

Styling file inputs is a problem web developers have been trying to solve for years. With this workaround we finally have a solid, semantic and accessible solution.

4 comments on “Styling File Inputs”.

Debbie

# Aug 29, 2019

I’ve been struggling to get this working and found your tutorial. Sadly I’m finding your demo does not work with keyboard navigation. It works visually with hover but it does not work in Chrome, Safari, or Firefox on Mac with keyboard-only navigation.

Guy

# Jul 26, 2019

I agree, this really is awesome. I’d love to get it working with react styled components. Haven’t managed it yet though.

# Oct 10, 2019

I agree thanks for this awesome solutions

————————————–
https://bbbootstrap.com

Christie

# Mar 27, 2019

This is awesome, thanks for the post 🙂 However, I have a bunch of dynamically created upload buttons and when I use this with them, only the first instance of the upload field changes. I’m not super comfortable with Javascript, so how would I iterate through an array of inputs and have them each update with the name of the file that’s uploaded? I hope I am making sense here.

Join the conversation.

Your email address will not be published. Required fields are marked *

All comments posted on 'Styling File Inputs' are held for moderation and only published when on topic and not rude. Get a gold star if you actually read & follow these rules.

You may write comments in Markdown. This is the best way to post any code, inline like `<div>this</div>` or multiline blocks within triple backtick fences (```) with double new lines before and after.

Want to tell me something privately, like pointing out a typo or stuff like that? Contact Me.