Most of an HTML document can be designed (styled) using CSS. This allows designers to apply their own look and feel to everything on the page. Everything except for the HTML input element used for file uploads.

The file upload element will generally look like this, although it is different in every browser:image

There is no CSS that can be used to hide the file name while showing the button with a look and feel that matches the rest of the page. Even if the rest of the page uses the default style for button, the size of the button cannot be changed.

The Solution

A working copy of what is described below can be found HERE.

The solution to this problem has been described on may web sites. I tried the simplest of these solutions and it worked. Well, it worked for the Chrome browser. It didn’t quite work for IE 9.

The solution is to use some CSS positioning to create an element of the right look and feel under this file input element. Then change the opacity, not the visibility, of the file input element so that it cannot be seen. It will still work but the user will think that they are clicking on your customized element underneath it.

This in no way bypasses the security of the element because there is still no way to trick a user into selecting the wrong file and no way to pick the wrong file in any client or server code. An onchange event deals with displaying the selected file by getting the file path from the file input element and displaying it elsewhere. It needs to be modified to get rid of a fake path that is used in many browsers but that is simple.

On more problem that needed to be solved that others have dealt with is the problem of some browsers only allowing you to click on the button and not in that edit area. You want the user to only click on the button without knowing it. Since all browsers that I tested accept a click on the right side of the file input element, even if that is the text portion of the element, I move the file input element to keep the right edge of it under the mouse. This presented some problems related to knowing which of multiple file input elements were being moved.

What I will show in the code below is my implementation of a simple form with two of these file input elements mostly hidden. Change the opacity in the CSS to zero to completely hide them. This code ensures that the file input element is in the right place to get the button click that the user thinks applies to the visible underlying element.

<!DOCTYPE HTML>

<head>
    <style type="text/css">
        .previewfileinput
        {
            position: relative;
            bottom: 0;
            width: 282px;
            height: 29px;
            border-bottom-color: #777;
            margin-top: 8px;
            overflow: hidden;
        }

        .previewfileinput .inputoverlay
        {
            position: absolute;
            top: 0;
            left: 0;
            width: 282px;
            height: 29px;
            text-align: center;
            font-size: 13px;
            cursor: pointer;
        }

        #inputoverlaybutton
        {
            cursor: pointer;
        }

        #uploadfileselection
        {
            width: 282px;
            height: 29px;
            cursor: pointer;

        }

        .inputoverlay input
        {
            width: 282px;
            height: 29px;
            margin: 0;
            padding: 0;
        }

        .theform
        {
            position: relative;
            width: 300px;
            height: 300px;
        }

        .previewfileinput i
        {
            color: #777;
            position: relative;
        }

        .previewfileinput input[type="file"]
        {
            position: absolute;
            top: 0;
            left: 0;
            height: 29px;
            z-index: 2;
            opacity: .5;
        }
    </style>
        <script type="text/javascript">

            function MoveUploadElement( event, buttonId )
            {
                if( typeof event == 'undefined' ) event = window.event;
                if( typeof event.pageY == 'undefined' && typeof event.clientX == 'number' && document.documentElement )
                {
                    event.pageX = event.clientX + document.documentElement.scrollLeft;
                    event.pageY = event.clientY + document.documentElement.scrollTop;
                };

                var targ = ( event.currentTarget ) ? event.currentTarget : event.srcElement;

                var buttonelements = getElementsByClassName( targ, "uploadbutton" );
                var uploadfileelements = getElementsByClassName( targ, "uploadfile" );

                var ox = oy = 0;
                var elem = buttonelements[0];
                if( elem.offsetParent )
                {
                    ox = elem.offsetLeft;
                    oy = elem.offsetTop;
                    while( elem = elem.offsetParent )
                    {
                        ox += elem.offsetLeft;
                        oy += elem.offsetTop;
                    };
                }
                var elem = buttonelements[0];
                if( elem.offsetParent )
                {         ox += elem.scrollLeft;
                    oy += elem.scrollTop;         while( elem = elem.offsetParent )         {             ox -= elem.scrollLeft;             oy -= elem.scrollTop;         };
                }

                var x = event.pageX - ox;
                var y = event.pageY - oy;
                var w = uploadfileelements[0].offsetWidth;
                var h = uploadfileelements[0].offsetHeight;

                uploadfileelements[0].style.top = y - (h / 2)  + 'px';
                uploadfileelements[0].style.left = x - w + 2 + 'px';

                return true;
            }

            function getElementsByClassName( node, classname ) 
            {
                if( node.getElementsByClassName ) 
                {
                    return node.getElementsByClassName( classname );
                } 
                else 
                {
                    if ( node == null )
                      node = document;
                    var classElements = [];
                    var els = node.getElementsByTagName( "*" );
                    var elsLen = els.length;
                    var pattern = new RegExp("(^|\\s)" + classname + "(\\s|$)");
                    var i, j;

                    for( i = 0, j = 0; i < elsLen; i++ )
                    {
                        if ( pattern.test( els[i].className ) ) 
                        {
                            classElements[j] = els[i];
                            j++;
                        }
                    }
                    return classElements;
                }
            }            
        </script>
</head>
<html>
    <body>
            <form class="theform" action="upload.php" enctype="multipart/form-data" method="post">
                        <div class="previewfileinput" onmousemove="MoveUploadElement( event, 'inputoverlaybutton1' );">
                            <div class="inputoverlay"><input class="uploadbutton" id="inputoverlaybutton1" type="button" name="selectfile1" value="Select Upload File" /></div>
                            <input type="file" class="uploadfile" name="uploadfile1" id="uploadfileselection1" />
                        </div>
                        <div class="previewfileinput" onmousemove="MoveUploadElement( event, 'inputoverlaybutton2' );">
                            <div class="inputoverlay"><input class="uploadbutton" id="inputoverlaybutton2" type="button" name="selectfile2" value="Select Upload File" /></div>
                            <input type="file" class="uploadfile" name="uploadfile2" id="uploadfileselection2" />
                        </div>
            </form>
    </body>
</html>

The page that you will see if you try this looks like this in Chrome:

image

The “Choose File” button and the edit field next to it can be seen for debugging purposes. Again, change the opacity value to zero to hide this completely.

Also notice that the file input element button is to the left of the text area. Firefox and IE show the button on the right but Chrome allows a click on the right side as if the button were clicked so it is not a problem. In fact, it was IE that would let me click in the text area and type in a file name. At least it seemed that way which was why I implemented the element movement code.

When you move the mouse over the “Select Upload File” button, you will see the overlying file input element move. I’ll reiterate the reason for this; the clickable button area of that overlying file input element needs to stay under the mouse or the user would be able to click on areas in it that do not bring up the file selection dialog box. We really don’t want the user to click on the text area and start editing a file name that they cannot see in a text box that is invisible. I think it was IE 9 that let this happen. I do most of my development in Chrome these days but I check Chrome, IE, and Firefox, to make sure these things work as expected.

I’ll leave it to the reader to figure out some of the details of the source code. The important part that I could not find on other sites was the code I wrote to deal with there being multiple file input elements on the page. Also, I copied the getElementsByClassName() function from a place where it had been written but never tested. I tested it by disabling the ability to use the built-in function and then re-enabled that feature once the other code was fixed. It’s really quite stupid to write code that has two possible paths and not test both and to then post it on the internet. I saw this hoping that I tested the rest of this well enough to not be a hypocrite.

If you copy the source code, which you are free to do, you may need to replace some characters in it that were converted to HTML equivalents when it was written to this page.

Some of this code came from a web page that had other stuff on it. Ignore things that seem unnecessary since they were necessary on some other page. For instance, the width of the buttons was for that other page as was some of the other style information. This is also not representative of the best way to configure event listener functions but it worked well for the test page.

There is now (10/15/2012) a test page on my site for this feature as well as an image preview feature. The test page can be found at www.rectorsquid.com/previewtest.php.