117

I'm trying to style a file upload button to my personal preferences, but I couldn't find any really solid ways to do this without JS. I did find two other questions about this subject, but the answers there either involved JavaScript, or suggested Quirksmode's approach.

My major issue with this Quirksmode's approach is that the file button will still have the browser-defined dimensions, so it won't automatically adjust to whatever's used as button that's placed below it. I've made some code, based on it, but it will just take up the space the file button would normally take up, so it won't at all fill the parent div like I want it to.

HTML:

<div class="myLabel">
    <input type="file"/>
    <span>My Label</span>
</div>

CSS:

.myLabel {
    position: relative;
}
.myLabel input {
    position: absolute;
    z-index: 2;
    opacity: 0;
    width: 100%;
    height: 100%;
}

This fiddle demonstrates how this approach is quite flawed. In Chrome, clicking the !! below the second demo button will open the file dialog anyway, but also in all other browsers, the file button doesn't take up the correct areas of the button.

Is there any more solid way to style the file upload button, without any JavaScript, and preferably using as little 'hacky' coding as possible (since hacking usually brings other problems along with it, such as the ones in the fiddle)?

Community
  • 1
  • 1
Joeytje50
  • 18,636
  • 15
  • 63
  • 95
  • Use quirsmode but with a big font-size. see my answer. – regisbsb Nov 30 '14 at 20:04
  • 1
    [Tympanus/codrops](http://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/) offers an excellent tutorial on styling file inputs while maintaining them accessible and navigable with the keyoard. – Dan Dascalescu Jul 27 '16 at 21:05

7 Answers7

302

I'm posting this because (to my surprise) there was no other place I could find that recommended this.

There's a really easy way to do this, without restricting you to browser-defined input dimensions. Just use the <label> tag around a hidden file upload button. This allows for even more freedom in styling than the styling allowed via webkit's built-in styling[1].

The label tag was made for the exact purpose of directing any click events on it to the child inputs[2], so using that, you won't require any JavaScript to direct the click event to the input button for you anymore. You'd to use something like the following:

label.myLabel input[type="file"] {
    position:absolute;
    top: -1000px;
}

/***** Example custom styling *****/
.myLabel {
    border: 2px solid #AAA;
    border-radius: 4px;
    padding: 2px 5px;
    margin: 2px;
    background: #DDD;
    display: inline-block;
}
.myLabel:hover {
    background: #CCC;
}
.myLabel:active {
    background: #CCF;
}
.myLabel :invalid + span {
    color: #A44;
}
.myLabel :valid + span {
    color: #4A4;
}
<label class="myLabel">
    <input type="file" required/>
    <span>My Label</span>
</label>

I've used a fixed position to hide the input, to make it work even in ancient versions of Internet Explorer (emulated IE8- refused to work on a visibility:hidden or display:none file-input). I've tested in emulated IE7 and up, and it worked perfectly.


  1. You can't use <button>s inside <label> tags unfortunately, so you'll have to define the styles for the buttons yourself. To me, this is the only downside to this approach.
  2. If the for attribute is defined, its value is used to trigger the input with the same id as the for attribute on the <label>.
Hassan Baig
  • 15,055
  • 27
  • 102
  • 205
Joeytje50
  • 18,636
  • 15
  • 63
  • 95
  • As to the issue about not having a button inside the label, I don't see this as a big issue. You can style any element to look like a button, even if it is not actually a button. The functionality when you click it is left to the `input[type=file]` anyway. – awe Jul 16 '14 at 09:46
  • 1
    Yeah, that's what I meant with the "you'll have to define the styles for the buttons yourself". You just add some styles to the ` – Joeytje50 Jul 16 '14 at 22:16
  • 8
    This is brilliant. As for buttons inside `label` elements it's perfectly fine. They can contain any phrasing content (which includes `input` and `button`) as long as it's not labelled by something else or is another label. Source: https://html.spec.whatwg.org/multipage/forms.html#the-label-element – Derek Johnson Nov 13 '14 at 22:53
  • 1
    @DerekJohnson True, it is indeed possible and allowed to do that, but it won't have the desired effect (at least not in every browser). For example: [this demo](http://jsbin.com/xeweyafixi/1/) (at least in Chrome) won't focus the input when you click the button, but it will focus if you click the plain text. Since you want the label to open the file popup, you'll need to use something other than a ` – Joeytje50 Nov 15 '14 at 23:14
  • 1
    it does not work on ie8 and ie7 – regisbsb Nov 30 '14 at 16:40
  • 1
    @regisbsb That's probably because StackOverflow's snippet feature doesn't work in those browsers. I tested this code in IE7 once though, and it worked fine there. Please check http://jsfiddle.net/run4s/1/ for a demo that does work in older IE versions. – Joeytje50 Nov 30 '14 at 16:45
  • I tested in my solution and still does not work. – regisbsb Nov 30 '14 at 18:58
  • 2
    @regisbsb I've just tested [this jsfiddle](http://jsfiddle.net/run4s/1/) in IE7&8 again, and indeed IE7 doesn't seem to work (anymore?), but IE8 works just fine. What is the code that doesn't work for you? PS: IE7 is used by [only 0.08% of people](http://caniuse.com/usage_table.php), at the time of writing, so I wouldn't worry about that. Anyway, IE8 should still work just fine, so could you send me a link of a jsfiddle/jsbin/etc of the code that doesn't work in IE8? – Joeytje50 Nov 30 '14 at 19:11
  • This amazing solution cut away lots of akward do-this-if-that-browser code from our codebase. Thanks! – ronkot Jan 08 '15 at 08:59
  • 3
    This works great for me, my only issue is that after a file is selected, it doesn't actually show that a file has been selected. Is there any way to show an indicator that a file has been chosen? – Dylan Vester Feb 27 '15 at 21:27
  • 2
    That's what's done with the `:valid` and `:invalid` code in the CSS. Alternatively you could change the text using something like [this](http://jsbin.com/kedajupuwa/1/edit?html,css,output). You can't show what the selected file is exactly though, this way. – Joeytje50 Feb 28 '15 at 01:54
  • Is there a way to show the link of the uploaded file beside as it does in the default? Or grab it using JS and showing it separately? That would make it a 100% working solution. Right now the user does not have any idea if the file has been added or not. – RP McMurphy Nov 07 '17 at 13:39
  • @RPMcMurphy yes it is possible to do this with JavaScript through reading the `input`'s `value` property. Besides that, the user can know whether their file has been selected or not by the color of the input. It changes color (red->green) now once the file has been selected. – Joeytje50 Nov 08 '17 at 14:02
  • Thanks much. Color change helps and I'll grab the value too. :) – RP McMurphy Nov 08 '17 at 20:45
  • This solution works with `position:absolute` as well. Why does that matter? It matters because `position:absolute` has better browser coverage than `position:fixed`. – Hassan Baig Apr 22 '18 at 21:06
  • @Joeytje50, it's not displaying the file name – Naren Verma Sep 11 '18 at 04:34
14

Please find below a way that works on all browsers. Basically I put the input on top the image. I make it huge using font-size so the user is always clicking the upload button.

.myFile {
  position: relative;
  overflow: hidden;
  float: left;
  clear: left;
}
.myFile input[type="file"] {
  display: block;
  position: absolute;
  top: 0;
  right: 0;
  opacity: 0;
  font-size: 100px;
  filter: alpha(opacity=0);
  cursor: pointer;
}
<label class="myFile">
  <img src="http://wscont1.apps.microsoft.com/winstore/1x/c37a9d99-6698-4339-acf3-c01daa75fb65/Icon.13385.png" alt="" />
  <input type="file" />
</label>
regisbsb
  • 3,664
  • 2
  • 35
  • 41
  • 1
    I'm going to have to say this is not an answer to the original question, which explicitly mentions [this approach by Quirksmode](http://www.quirksmode.org/dom/inputfile.html), which uses the same basic principle as your answer. But, if you want to use this, I'm not going to stop you of course, but then I'd recommend not using the ` – Joeytje50 Nov 30 '14 at 19:20
  • He was complaing about that it won't resize, but now with font-size it will. Quote: "My major issue with this Quirksmode's approach is that the file button will still have the browser-defined dimensions, so it won't automatically adjust to whatever's used as button that's placed below it. I've made some code, based on it, but it will just take up the space the file button would normally take up, so it won't at all fill the parent div like I want it to." – regisbsb Nov 30 '14 at 20:02
  • 1
    This solution with a large font has the disadvantage that if you have clickable elements near the file upload button, you can't click on them because the clickable area is covering the elements beneath... not a solution to me! – basZero Apr 19 '17 at 15:05
  • i) Why have both `opacity:0` and `filter: alpha(opacity=0)`? It seems just `opacity:0` is good enough to make the file element invisible. ii) Secondly, how about using `font-size:1px` instead of 100px? The `img` inside the label is de facto acting as the input element (so the clickable area would be dictated by that), so how about making the input element very small and invisible? – Hassan Baig Apr 22 '18 at 20:55
  • This was meant to work on IE7 and IE8 – regisbsb Apr 25 '18 at 12:25
9

The best example is this one, No hiding, No jQuery, It's completely pure CSS

http://css-tricks.com/snippets/css/custom-file-input-styling-webkitblink/

.custom-file-input::-webkit-file-upload-button {
    visibility: hidden;
}

.custom-file-input::before {
    content: 'Select some files';
    display: inline-block;
    background: -webkit-linear-gradient(top, #f9f9f9, #e3e3e3);
    border: 1px solid #999;
    border-radius: 3px;
    padding: 5px 8px;
    outline: none;
    white-space: nowrap;
    -webkit-user-select: none;
    cursor: pointer;
    text-shadow: 1px 1px #fff;
    font-weight: 700;
    font-size: 10pt;
}

.custom-file-input:hover::before {
    border-color: black;
}

.custom-file-input:active::before {
    background: -webkit-linear-gradient(top, #e3e3e3, #f9f9f9);
}
<input type="file" class="custom-file-input">
mpen
  • 272,448
  • 266
  • 850
  • 1,236
Shamal Sandeep
  • 519
  • 4
  • 9
  • Also a very good solution. The only problem I can see with this is that *technically*, input elements don't have `::before` and `::after` pseudo-elements because they're void elements (they have no content). See [this SO question for more info](http://stackoverflow.com/q/4574912/1256925). – Joeytje50 Aug 16 '14 at 21:14
  • 2
    This causes completely different behaviour in IE and chrome. Isn't the idea is to make it browser agnostic? – kaybee99 Oct 23 '15 at 15:34
  • 6
    Also, it doesn't work in Firefox, at all, ever. Because Firefox is based on Gecko, not webkit. – Cullub Dec 07 '15 at 19:02
2

This seems to take care of business pretty well. A fidde is here:

HTML

<label for="upload-file">A proper input label</label>

<div class="upload-button">

    <div class="upload-cover">
         Upload text or whatevers
    </div>

    <!-- this is later in the source so it'll be "on top" -->
    <input name="upload-file" type="file" />

</div> <!-- .upload-button -->

CSS

/* first things first - get your box-model straight*/
*, *:before, *:after {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

label {
    /* just positioning */
    float: left; 
    margin-bottom: .5em;
}

.upload-button {
    /* key */
    position: relative;
    overflow: hidden;

    /* just positioning */
    float: left; 
    clear: left;
}

.upload-cover { 
    /* basically just style this however you want - the overlaying file upload should spread out and fill whatever you turn this into */
    background-color: gray;
    text-align: center;
    padding: .5em 1em;
    border-radius: 2em;
    border: 5px solid rgba(0,0,0,.1);

    cursor: pointer;
}

.upload-button input[type="file"] {
    display: block;
    position: absolute;
    top: 0; left: 0;
    margin-left: -75px; /* gets that button with no-pointer-cursor off to the left and out of the way */
    width: 200%; /* over compensates for the above - I would use calc or sass math if not here*/
    height: 100%;
    opacity: .2; /* left this here so you could see. Make it 0 */
    cursor: pointer;
    border: 1px solid red;
}

.upload-button:hover .upload-cover {
    background-color: #f06;
}
sheriffderek
  • 8,848
  • 6
  • 43
  • 70
  • Messed around with that a bit more here: http://codepen.io/sheriffderek/pen/JqlDB – sheriffderek Feb 18 '14 at 05:15
  • Nice solution. Not really a compact solution, but still really nice. Just one tiny comment though: you should include a `filter` opacity for older IE versions (in which it won't look as good anyway, but then again, it's IE). Other than that, this is a really nice alternative. – Joeytje50 Feb 18 '14 at 10:54
  • Good point. I don't support IE 8 or below anymore in my projects. But good to know for certain client stuff. The `label` solution seems to be better after all. I was using `display:none;` and then making the label `inline-block` or `block` with some radio buttons. Seems pretty awesome - My solution isn't super tiny, but - realistically - how many upload buttons would you have etc... I guess you could have a combo of the 2 with some conditions in a user-reset and cover all of your bases. – sheriffderek Feb 18 '14 at 18:10
  • 2
    http://codepen.io/sheriffderek/pen/177e86a195b98a2058921e4ef859cd73 – sheriffderek Feb 18 '14 at 18:12
  • @sherriffderek oh I just love using radios for interactivity too. I've [even made a whole MediaWiki extension](https://www.mediawiki.org/wiki/Extension:Tabber) based on just that. Sadly, [on Android Browser it doesn't update that well](http://stackoverflow.com/q/21357641/1256925), so you'd have to build in a fallback for that (if you wanna support the extra [quarter of mobile users](http://gs.statcounter.com/#mobile+tablet-browser-ww-monthly-201301-201401)). – Joeytje50 Feb 18 '14 at 18:53
  • does not work on ie8 – regisbsb Nov 30 '14 at 19:09
  • 1
    @regisbsb - It also doesn't work on Atari - or NES – sheriffderek Dec 01 '14 at 19:41
1

Any easy way to cover ALL file inputs is to just style your input[type=button] and drop this in globally to turn file inputs into buttons:

$(document).ready(function() {
    $("input[type=file]").each(function () {
        var thisInput$ = $(this);
        var newElement = $("<input type='button' value='Choose File' />");
        newElement.click(function() {
            thisInput$.click();
        });
        thisInput$.after(newElement);
        thisInput$.hide();
    });
});

Here's some sample button CSS that I got from http://cssdeck.com/labs/beautiful-flat-buttons:

input[type=button] {
  position: relative;
  vertical-align: top;
  width: 100%;
  height: 60px;
  padding: 0;
  font-size: 22px;
  color:white;
  text-align: center;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  background: #454545;
  border: 0;
  border-bottom: 2px solid #2f2e2e;
  cursor: pointer;
  -webkit-box-shadow: inset 0 -2px #2f2e2e;
  box-shadow: inset 0 -2px #2f2e2e;
}
input[type=button]:active {
  top: 1px;
  outline: none;
  -webkit-box-shadow: none;
  box-shadow: none;
}
Cymricus
  • 350
  • 2
  • 8
0

I just came across this problem and have written a solution for those of you who are using Angular. You can write a custom directive composed of a container, a button, and an input element with type file. With CSS you then place the input over the custom button but with opacity 0. You set the containers height and width to exactly the offset width and height of the button and the input's height and width to 100% of the container.

the directive

angular.module('myCoolApp')
  .directive('fileButton', function () {
    return {
      templateUrl: 'components/directives/fileButton/fileButton.html',
      restrict: 'E',
      link: function (scope, element, attributes) {

        var container = angular.element('.file-upload-container');
        var button = angular.element('.file-upload-button');

        container.css({
            position: 'relative',
            overflow: 'hidden',
            width: button.offsetWidth,
            height: button.offsetHeight
        })

      }

    };
  });

a jade template if you are using jade

div(class="file-upload-container") 
    button(class="file-upload-button") +
    input#file-upload(class="file-upload-input", type='file', onchange="doSomethingWhenFileIsSelected()")  

the same template in html if you are using html

<div class="file-upload-container">
   <button class="file-upload-button"></button>
   <input class="file-upload-input" id="file-upload" type="file" onchange="doSomethingWhenFileIsSelected()" /> 
</div>

the css

.file-upload-button {
    margin-top: 40px;
    padding: 30px;
    border: 1px solid black;
    height: 100px;
    width: 100px;
    background: transparent;
    font-size: 66px;
    padding-top: 0px;
    border-radius: 5px;
    border: 2px solid rgb(255, 228, 0); 
    color: rgb(255, 228, 0);
}

.file-upload-input {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}
Benjamin Conant
  • 1,684
  • 1
  • 14
  • 17
-2

It's also easy to style the label if you are working with Bootstrap and LESS:

label {
    .btn();
    .btn-primary();

    > input[type="file"] {
        display: none;
    }
}
sdvnksv
  • 9,350
  • 18
  • 56
  • 108
  • `display: none` will remove the input from tab order. [Using ` – Dan Dascalescu Jul 27 '16 at 21:03