Using positioned background images (a.k.a CSS Sprites) has a number of benefits:
- Performance. One of the performance recommendations made by Yahoo’s performance team is to reduce HTTP requests. One way to do this is using the CSS Sprite technique — combining many images into a single background image.
- Skin-ability. With the img tag the image URL is in HTML markup where it is hard to change. With CSS Sprites the image URL is in a CSS file making it more convenient to change. Sure you can change the contents of the original file but there are reasons for changing the URL. It is not as easy to move the images to a different server or group them in different image files when the URL is not in CSS.
However there are drawbacks. The biggest is accessibility, specifically high contrast mode. In high contrast mode all background images and colors are ignored — replaced with high contrast colors such as white on black or black on white. The other issue is that background images are not always printed.
The prevailing accessibility advice is to not use background images for functional images. The underlying problem is that there is no way in HTML/CSS to identify a background image as being functional. They are all considered decorative. It is also true that not all img tag images are functional but again there is no way to distinguish them for the purpose of high contrast mode. So high contrast mode makes a reasonable assumption that background images are decoration to be removed and img images are functional and must be shown. From here on I’ll call functional images icons. They either convey important information or are interactive (like a toolbar button for example).
I have seen recommendations that functional background images should be replaced with text when high contrast mode is detected. This does not seem right to me at all. A desktop app does not change its toolbar button icons to text in high contrast mode. The assumption is that icons are already designed to have reasonable contrast.
It also just feels right to me that the icon URLs should come from CSS.
Since I care about performance and accessibility I’m not happy with this conflict. I want a solution that puts the icon URL in CSS, works in high contrast mode and allows me to combine icons into a single image file. Here is what I came up with.
My typical markup for an icon is something like this:
<button><span class="icon edit"></span></button> <button><span class="icon delete"></span></button>
The basic style is:
span.icon { display: -moz-inline-box; display: inline-block; width: 16px; height: 16px; } button span.icon.edit { background: transparent url(images/toolbar.png) no-repeat 0px -32px; } button span.icon.delete { background: transparent url(images/toolbar.png) no-repeat 0px -16px; }
This creates a button with an edit icon assuming that toolbar.png is a sprite image with the edit icon at offset 0, 32 etc. I left out extras like a title tooltip and hover and active states for the button icon to keep things simple. This works nicely until high contrast mode is turned on.
You can use the same sprite image in an img element and position it relative to a fixed size parent that has overflow:hidden to achieve similar functionality to a background image.
For example:
<button><span class="icon edit"><img alt="" src="images/toolbar.png"></span></button> ...
The style to make this work is:
span.icon { display: -moz-inline-box; display: inline-block; width: 16px; height: 16px; overflow: hidden; position: relative; } button span.icon.edit img { position: relative; left: 0px; top: -32px; } ...
This works in high contrast mode (as well as normal mode) and has multiple icons in a single image but the image URL is in the markup not in the stylesheet. What I want to do is use JavaScript to take the image URL from CSS and dynamically add the img element as a child of the icon span. The html mark up and stylesheet will be the same as the first example above except that the stylesheet will have these additional styles:
span.icon.edit img { top: -32px; left: 0px; } span.icon.delete img { top: -16px; left: 0px; }
These additional rules are not too much extra work considering you will usually have additional rules to adjust the position on hover and active states anyway.
My first attempt tried to get the URL from the current styles using jQuery like so: $(…).css(“background-image”). The trouble is that in high contrast mode the effective current background image is “none” even though the CSS file does have a background image. This makes sense — the program should “see” what the user sees. The solution is to get the actual background image directly from the CSS rules. This takes a little fiddling to get it to work cross browser. Some browsers return the full URL and some return exactly what is in the CSS file. Some times its wrapped in quotes.
This test page shows an example of the traditional CSS Sprite technique as well as my jQuery enhanced high contrast mode compatible version. All the styles and JavaScript are in the html file to make it easy to see how it works. It also contains code to detect high contrast mode. One option is to only make the dynamic change if high contrast mode is detected but that is not the approach I took in the test page.
View the page with high contrast mode turned on and off to see the difference. Note: not all browsers or operating systems support high contrast mode. Firefox and IE on Windows do. It is also important that the dynamic changes work even in browsers that don’t support it (by work I mean that the icons look right and are functional, not that high contrast mode magically starts working in browsers that don’t support it). I tested on Windows with IE8, FF3.5, Safari 3.2 and 4.0, Opera 10 and Chrome and found that they all worked. I also tested on Linux with FF3 and found no problems. I don’t have a Mac to test with. I tested with IE6 and found that it did not work. More investigation is needed but IE6 is not a high priority for me right now. I need to test with IE7 but don’t have it handy right now.
Feedback on how it works in other browsers and operating systems would be very helpful.
I did not do any performance testing. The assumption is that moving the image from the background to an img tag will not cause the image to be requested from the server. The Firebug Net tab indicates that it is fetched just once. The page seems to render fast enough with no flashing. More testing is needed but I think the principal is sound. The script could use some improvement to be more robust especially for much older browsers so they at least fall back to using the CSS sprites as is. I bet there are other cases of relative URLs to test for as well.
Keep in mind that the test page is meant to demonstrate just this technique. It has a bunch of other accessibility no-nos such as the disclose icons are not focusable, keyboard accessible, and have no screen reader accessible label text.