Styling File Inputs with CSS and the DOM
File inputs (<input type="file" />
) are the bane of beautiful form design. No rendering engine provides the granular control over their presentation designers desire. This simple, three-part progressive enhancement provides the markup, CSS, and JavaScript to address the long-standing irritation.

Before we get down to the nitty gritty details, check out the demo or, if you’re a Mint user, check out your settings page in the Mint Account Center (requires login).
The Markup
<label class="cabinet">
<input type="file" class="file" />
</label>
The file input is given a class of file
and wrapped in an element with a class of cabinet
. (The type of wrapper element and actual class names are not set in stone and can be changed via properties of the SI.Files
object to avoid conflicts as necessary.)
The CSS
.SI-FILES-STYLIZED label.cabinet
{
width: 79px;
height: 22px;
background: url(btn-choose-file.gif) 0 0 no-repeat;
display: block;
overflow: hidden;
cursor: pointer;
}
.SI-FILES-STYLIZED label.cabinet input.file
{
position: relative;
height: 100%;
width: auto;
opacity: 0;
-moz-opacity: 0;
filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);
}
The first three properties of the wrapper element style should be changed to reflect the width, height and url of your custom button image. Everything else can be left alone. (Different buttons can be used for different file inputs by adding an additional class to the wrapper element and styling accordingly.)
The JavaScript
First include the SI.Files
library. Simple enough:
<script type="text/javascript" src="/path/to/si.files.js"></script>
Then, either in your onload event handler or at some point after your file inputs appear in the source, call:
SI.Files.stylizeAll();
The library also offers two alternate methods of styling file inputs. Given an element id:
SI.Files.stylizeById('input-id');
Or given an element node:
SI.Files.stylize(HTMLInputNode);
Both alternates are useful when the form that contains your file input is loaded via Ajax.
How it works
We start with a simple replacement. The custom button image is set as the background-image of our wrapper element and dimensions are set to match.
Next we set the opacity of the file input itself to zero, effectively making it invisible but still clickable (something that can’t be achieved with display: none;
or visibility: hidden;
).
Finally, the JavaScript keeps the button portion of the the file input underneath the pointer whenever the mouse enters the wrapper element. A class of SI-FILES-STYLIZED
(also configurable) is applied to the html
element of the page for use as a styling context for compatible browsers.
Compatibility
This solution is known to work in:
- IE 5.5+
- Firefox 1.5+
- Safari 2+
It is known to degrade gracefully in:
- Opera
- IE 5.01
The SI.Files library and example files have been downloaded 34015 times.
029 Comments
Genius. Pure and unadulterated evil genius.
Once Again, Shaun Inman is my hero for what would otherwise seem to be an impossible situation.
Thanks guys. There’s definitely room for improvement.
Currently, there’s no indication when a file is already selected.
With an additional element sitting inside the wrapper you could use HTML text and your favorite bulletproof technique to make the button resize with the rest of the page.
I also wonder if screen readers would apply the visual opacity change like they erroneously do with
display: none;
.It just occurred to me that this approach (sans JavaScript) would also work for styling
<input type="submit">
if you’re adverse to<input type="image">
(I personally don’t care for the way they unnecessarily pass the x/y coordinates as part of the query string when used withGET
).Well, I don’t pretend to know a lot about the JS running behind it, since I dabble mostly in HTML & CSS only, however from a visual perspective, I worked something up in Photoshop for a possible upload view that shows you the file that has been selected and then an upload option. Link
I’ve used a similar approach for getting a custom select menu (set it as the background image, and set opactiy to 0). Works great! Good stuff here, man.
Nice one. As with Jeff above, I’ve used this type of technique as part of a custom select menu. Now I can make file uploading just as pretty.
I’ve been meaning to dive into the way flickr allows you to upload files and see how they are doing things but I’ve not yet had the time.
One question about the CSS:
Why so explicit with: filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);
Why not just: filter: alpha(opacity=0);
I just do it the way Microsoft documents it.
Shaun,
One other thing. Unless I’m doing something wrong, the cursor: pointer doesn’t seem to be kicking in in Firefox 2 (at least not in the Mint settings page).
Any idea why?
Nope. Just goes to show how limited the styling options are for file inputs—you can’t even change the cursor. IIRC, Safari is the only browser that honors the cursor property on a file input.
This doesn’t seem to work in Safari 3.0.3. Can someone else verify this, because I might just be missing something. At any rate, the mini screenshot looks great.
Good fucking idea.
Very handy. I was doing something similar (minus the JS) for a submit button today and will definitely try this out for file inputs.
As said in comment 3, this has one major usability problem: the user doesn’t have any feedback about which file has been selected.
And another usability issue: no feedback about the focus state.
Damn UR sexy
Couldn’t you alter the graphic to represent a value inside it (and even list the file path) by attaching an onchange listener to the input field?
Just wondering… does this work without changes with any of the drag-and-drop file upload plugins for Firefox?
This technique was invented by Michael McGrady years ago. michaelmcgrady.com/file_browse_images.jsp
yep, in safari 3.03 its doesnt work
What, exactly, is the JavaScript doing? I don’t see why it’s necessary to make this work.
“This simple, three-part progressive enhancement provides the markup, CSS, and JavaScript to address the long-standing irritation.”
Chill, jotos. He’s not claiming he invented it.
I like what you’re doing here, but as long as you’re taking away usability it’s not worth the hassle.
Is there a way for the user to know which file he has selected?
Is there a way for the user to change his mind and revoke the file without resetting/reloading the form?
Here is how you could have done it: disney.co.uk/toontown/contactus.html (Select “technical question”, then look for the “Screenshot” section.)
Darryl McAdams:
Yes, Wabbitseason. I like that implementation.
Opera 9 does have support for opacity, in the DOM too!
Instead of having narrow, hardcoded and not future-proof check for a certain browsers, you’d better use object detection that does specifically check for opacity support:
if (some_element.style.opacity !== undefined) {works}
hi shaun, great tips, but like someone sayd, there’s no pointer when rollover, do you think it’s possible to use an additional image on rollover ?
AFAIK Safari 3 is still in beta. I’ll worry about it once it’s officially released. That said it works fine for me in Safari 3.03b on XP.
Testing for
elem.style.opacity
doesn’t work (did you even bother testing this superior alternative before commenting kL?). Even if it did work, IE PC 5.5 & 6 would fail the test.Before I added the code to omit Opera, opacity was observed not to work in Opera 9.22 Mac. Opera users make up less than 2% of the visitors on the site this technique was developed for. It degrades gracefully. My job is done.
Seems I forgot my usual disclaimer: this script is offered as is. It works for me. If it doesn’t work for you—technically, morally, whatever—don’t use it. Like the Michael McGrady/Disney method of styling inputs better? Use it. Think you can improve on my approach? Do it. Once you’ve done the work, share it. And then get on with your life.
Unfortunately, in IE7 the file control can track outside of the container-tag. Adding the following code to your javascript solves the problem: