Image Replacement with CSS Based Rollovers

Jan 19 2005

So, you’ve written a page using image replacement but you need rollover effects as well? That can be done. (For those unaware, “image replacement” is a CSS concept, implementable in several ways, where a page’s HTML has regular tags which are then dynamically replaced with image-based headers through CSS.) And, of the various image-replacement techniques the Gilder/Levin technique is my favorite for general use as it preserves text accessibility even in the unlikely event that a user has CSS turned on but images turned off. As you might guess, all of the image-replacement techniques are based on CSS background images; and, in the case of rollovers, you would think that you could just change the background-image on a:hover. That works in Firefox (no surprise) but not in IE (which is not a huge surprise, either).

Oddly enough, IE seems able to change an anchor’s background image through :hover, but it doesn’t change the image back to the “off” state after the user rolls off. Though we ran into this on a page for a client, I created a minimized test case (encompassing just the UL and little else) but without cooperation from IE. So, I turned to other means and was able to achieve the effect with a combination of pixy’s CSS rollovers and the Revised Phark image-replacement technique.

Here’s how it came together. I’ll go over the HTML first and then the CSS. I’ll assume the navigation items are Alice, Bob and Carl for the sake of the example.

So, the navigation items are just in an unordered list here, as that was most semantically appropriate in our case. Then, the CSS begins. First some generally styling for the list to place it inline:


margin: 0em;
padding: 0em;

/* You’ll also need to add width:100% here (or whichever value you prefer) if you’re absolutely positioning your list */
/* width: 100%; */


ul#primary-nav li

float: left;
display: inline;
list-style-type: none;

/* Widths are required since these elements are floated. This is just an example width; and feel free to define specific widths for each LI (via their IDs) if you need to. */
width: 107px;

/* This provides space between each item */
margin-right: 4px; /* This is just an example margin */


ul#primary-nav li

/* “!important” shouldn’t be necessary here but IE5.0 acts goofily without it. */
height: 34px !important; /* This is an example value representing the height of each image */

/* If your primary-nav is absolutely positioned, this ensures that nothing bleeds out of the primary-nav. Without this, the “stacked” background bits could stick out from the bottom. */

/* hide overflow:hidden from IE5/Mac */
/* */
overflow: hidden;
/* */


/* The last item in the list (well, our list, anyway) has no right margin */
ul#primary-nav li#nav-carl

margin-right: 0px;


There’re also some properties which apply to each of the h2 and anchor tags within the list:

/* This ensures that the rollover containers, h2 and a, fill the
li boxes */
ul#primary-nav li h2,
ul#primary-nav li h2 a

display: block;
width: 100%;
height: 100%;


And, then text-hiding aspect of the image-replacement is applied:

/* This hides the text via the Phark text-hiding method: */
ul#primary-nav li h2 span

text-indent: -5000px;


Now the interesting part begins ;) . First, make sure that you’ve constructed the “stacked images” for the rollovers as outlined in the pixy rollover technique (we stacked ours vertically, with the “on” state on bottom). Then it’s just a matter of defining three more IDs — well, three more IDs for each navigation item.

/* ================= Primary Nav: Alice ================ */

/* The “Alice” link */

ul#primary-nav li#nav-alice h2

/* Our images were 40px high, so a 20px shift shows only the bottom “on” half */
background: url(../images/nav/search-background.gif) 0 -20px no-repeat; /* On state image */


ul#primary-nav li#nav-alice h2 a

background: url(../images/nav/search-background.gif) top left no-repeat; /* Off state image */


/* The “Alice” link, on state */
ul#primary-nav li#nav-alice h2 a:hover

background-image: none;


So, what happens is that each LI gets a background image equal to the “on” state, but that’s normally covered up by the anchor which has a background image equal to the “off” state. Then, the :hover pseudo-class for the anchor removes the background image, allowing the “on” state underneath to show through.

As one last bit of code, suppose that you want to have a setting for the “current page” where the “on” state would always be displayed? (For instance, the “Alice” header could always be on while users are in the “Alice” section.) That’s just a matter of adding a comma and another selector to that last rule:

/* The “Alice” link, on state */
ul#primary-nav li#nav-alice h2 a:hover,
ul#primary-nav li#nav-alice h2 a#current-page

background-image: none;


To make use of that, just add the ID “current-page” to the anchor tag on any page which you want to have an always-on header. Going on the example HTML from before, that would look like this:

I’m still not sure why IE protested at the use of swapping-backgrounds, but this set of code worked out for us.

5 Comments to “Image Replacement with CSS Based Rollovers”

  1. Hi Alex,
    A very nice article, thanx. I have one question though: you use the extra spans, because of the use of the Gilder/Levin technique. What if I would use another technique, like the method of Stuart Langridge. In that case, I could do without the extra spans. The only drawback would be the css-on, images-off situation, am I correct?

    By Matthijs on March 9th, 2005 at 3:47 am
  2. Maybe not so nice. I just tested your method, and it seems to work in IE6, but not in FF, Moilla or Opera. And I don’t like the extra H2 tags. Shouldn’t it just be li a href span etc?

    By Matthijs on March 9th, 2005 at 4:19 am
  3. Hi Matthijs!

    I’ll try to address each of your questions. The first which you brought up was the idea of using the Leahy/Langridge image replacement technique rather than the Phark technique. And, while that would probably work for most scenarios, I had some reasons for not going for that one.

    Firstly, it takes advantage of the CSS box model, which IE 5.x can’t understand, requiring a box model hack for each image-replaced header. And, while Langridge added that without much extra code, it still means making two CSS changes if a header’s dimensions change.

    Another concern is that it makes use of “overflow: hidden”. And, while that’s perfectly legal CSS, IE/Mac has generally had difficultly with that property (generally resulting in hiding the element which has “overflow: hidden” rather than hiding only its overflow). I’m not ruling out that Langridge may have figured out how to avoid triggering this bug, but I’d have to check it on IE5/Mac to be sure.

    Then again, if your project doesn’t require IE5/Mac support in the first place (and, really, that’s a sensible position to take) then the Leahy/Langridge technique becomes all the more tempting. Perhaps I’ll check the Leahy/Langridge demos on one of the Macs here to see how it holds up.

    On to your second comment — which was that the code didn’t work for you ;) . While I based the example code in the post on some actual code which we used on a client’s site, I believe that I may have inadvertently omitted some properties which happened to be cascaded in our original implementation.

    I’ve updated the entry to include those and I’ve also included them below:

    - I added a specific height to both the unordered list (ul#primary-nav) and its children list items (ul#primary-nav li) to ensure that the list would remain as tall as intended (especially in IE 5.0).

    - I also added “overflow: hidden” (which IE5/Mac doesn’t see, natch) to the unordered list (ul#primary-nav) and its children list items (ul#primary-nav li) to be sure that none of the elements leaked out from the list’s boundaries.

    - I extracted some properties (“width” and “height”, I believe) which appeared in each of the individual list item sections (li#nav-alice, li#nav-bob, and so on) and created a general rule instead which applied those properties to all list items. (This is the rule in the entry which begins with the comment “This ensures that the rollover containers, h2 and a, fill the li boxes”).

    - Lastly, I added “display: block” to the text-hiding rule as “text-indent” is only valid on block-level elements.

    You also asked about whether H2 tags are necessary. And, while I believe it may be possible to omit those tags, many of the CSS rules would have to be rewritten to accommodate the missing tags. Additionally, there are SEO benefits to using header tags within your primary navigation. Search engines tend to give more weight to H* tags (H1, H2, and so on) and someone searching Google for a phrase which was used in your primary nav would be more likely to see your site in his/her search results if that term was enclosed in a header tag.

    I hope this helps!

    By Alex on April 8th, 2005 at 12:38 pm
  4. On the off-chance anyone is reading comments three years after this article was published, I just want to say a big thankyou. It solved a problem that no-one else seemed to have encountered, let alone fixed, just when I was giving up on pixy rollovers for good!

    By John Proudlove on February 24th, 2008 at 3:45 pm
  5. Me too! Thanks!

    By Ale on December 1st, 2008 at 6:29 pm

Leave a Reply