Cross-browser custom styling for file upload button [duplicate]
Asked Answered
W

7

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

Welldisposed answered 18/2, 2014 at 0:52 Comment(2)
Use quirsmode but with a big font-size. see my answer.Ponceau
Tympanus/codrops offers an excellent tutorial on styling file inputs while maintaining them accessible and navigable with the keyoard.Alemanni
W
304

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>.
Welldisposed answered 18/2, 2014 at 0:52 Comment(16)
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.Tosh
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 <label> tag to make it look like a button.Welldisposed
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: html.spec.whatwg.org/multipage/forms.html#the-label-elementUranus
@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 (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 <button>.Welldisposed
it does not work on ie8 and ie7Ponceau
@Ponceau 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 jsfiddle.net/run4s/1 for a demo that does work in older IE versions.Welldisposed
I tested in my solution and still does not work.Ponceau
@Ponceau I've just tested this jsfiddle 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, 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?Welldisposed
This amazing solution cut away lots of akward do-this-if-that-browser code from our codebase. Thanks!Mccabe
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?Fortuneteller
That's what's done with the :valid and :invalid code in the CSS. Alternatively you could change the text using something like this. You can't show what the selected file is exactly though, this way.Welldisposed
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.Laureen
@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.Welldisposed
Thanks much. Color change helps and I'll grab the value too. :)Laureen
This solution works with position:absolute as well. Why does that matter? It matters because position:absolute has better browser coverage than position:fixed.Filide
@Joeytje50, it's not displaying the file nameGadoid
P
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>
Ponceau answered 30/11, 2014 at 19:9 Comment(5)
I'm going to have to say this is not an answer to the original question, which explicitly mentions this approach by Quirksmode, 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 <label> tag at all (but use a <div> instead, or something), since it doesn't add anything to this situation.Welldisposed
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."Ponceau
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!Flavourful
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?Filide
This was meant to work on IE7 and IE8Ponceau
H
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">
Halfcock answered 6/8, 2014 at 1:59 Comment(3)
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.Welldisposed
This causes completely different behaviour in IE and chrome. Isn't the idea is to make it browser agnostic?Hamnet
Also, it doesn't work in Firefox, at all, ever. Because Firefox is based on Gecko, not webkit.Derive
P
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;
}
Pyrrhuloxia answered 18/2, 2014 at 4:52 Comment(7)
Messed around with that a bit more here: codepen.io/sheriffderek/pen/JqlDBPyrrhuloxia
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.Welldisposed
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.Pyrrhuloxia
codepen.io/sheriffderek/pen/177e86a195b98a2058921e4ef859cd73Pyrrhuloxia
@sherriffderek oh I just love using radios for interactivity too. I've even made a whole MediaWiki extension based on just that. Sadly, on Android Browser it doesn't update that well, so you'd have to build in a fallback for that (if you wanna support the extra quarter of mobile users).Welldisposed
does not work on ie8Ponceau
@Ponceau - It also doesn't work on Atari - or NESPyrrhuloxia
K
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;
}
Kooky answered 11/5, 2015 at 15:7 Comment(0)
C
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;
}
Characterization answered 7/5, 2015 at 21:11 Comment(0)
T
-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;
    }
}
Tetzel answered 20/4, 2016 at 8:13 Comment(1)
display: none will remove the input from tab order. Using <label> (as shown by Tympanus) is semantic but needs some refinements.Alemanni

© 2022 - 2024 — McMap. All rights reserved.