So my co-worker at work today was slicing a design, and the designer said that the navigation had to be images. Being the good little accessibility people that we were, we were trying to figure out a way to use images, but have text also show up when images are disabled.

So my co-worker went with the quick fix (for now), a JavaScript onmouseover solution, and I told him that it was lame. I told him there had to be a way to use CSS to do it. I didn't have time to do it at work, so I played with it, and I have found a solution.

The Markup

Ok, so everyone knows to mark-up navigation in an unordered list:

<ul id="nav">
 <li><a href="#">Text to Cover Here</a></li>
 <li><a href="#">Text to Cover Here</a></li>
 <li><a href="#">Text to Cover Here</a></li>
 <li><a href="#">Text to Cover Here</a></li>
</ul> 

Now, instead of placing an image in the anchor tag and then using JavaScript to change the hover state, I thought, why not just add an extra span at the end of the anchor tag. I chose the end, but it also works with it at the beginning. I think it makes more sense to have it at the end though, so that the text within the anchor tag is read first by screen readers and bots. So this is what our markup looks like now:

<ul id="nav">
 <li><a href="#">Text to Cover Here<span></span></a></li>
 <li><a href="#">Text to Cover Here<span></span></a></li>
 <li><a href="#">Text to Cover Here<span></span></a></li>
 <li><a href="#">Text to Cover Here<span></span></a></li>
</ul> 

Let’s Style It

So I started off with just some normal styles that you would apply to a navigation: zeroing out margins and paddings, floating it, removing the list-styling, and giving it a width.

Next, I floated the list items so that they would be in a line horizontally. This is where it finally gets interesting, I promise. I styled the anchor tag like so:

ul#nav li a {
 background: #FFFF99;
 display: block;
 height: 30px;
 padding: 0 5px;
 width: 115px;

Since this is just an example, I put a random background color on (to make sure it didn’t show through in the final example), and I gave them all the same width. Not a likely situation, but I didn’t feel like giving each list item an id. That should be self explanatory enough.

Now, when I think back to the talk that Eric Meyer at An Event Apart Boston, he kept stressing that to a browser, an element is just an element, and you can do anything to it. So, my plan was to just set the anchor tag to be relatively positioned so that it would contain the span.

The next step is to add the background image to the span. Now one thing that needs to be realized is that you cannot use a transparent image. But I don’t think that really causes much of a problem in most cases.

ul#nav li a span {
 background: url(/images/content/2008/01/nav.gif) no-repeat 0 -30px;
 cursor: pointer;
 display: block;
 height: 30px;
 left: 0;
 position: absolute;
 top: 0;
 width: 125px;

The only things to note from that is that I combined the normal and hover states into one awesome image (I wasn’t worried about how pretty it looked). I also had to add the cursor property for our best friend, IE6.

Ok, cool. We are on our way. Now we just need to shift the background image on the hover state:

ul#nav li a:hover span { background: url(/images/content/2008/01/nav.gif) no-repeat 0 0; } 

Voilà! It works like a charm. Check out the example.

But…

Did you check it in IE6? When I checked it in Firefox and IE7, everything worked beautifully. You can turn off the images and you get the text underneath.

When you do check it in IE6, you will notice that the hover states stay on. It’s very odd.

After a lot of tinkering, I finally found something that worked. If you add the following to the anchor when it is in its hovered state, it for some reason fixes it:

ul#nav li a:hover { background: 0 0; } 

I have no explanation, but it does not seem to have any adverse affects on other browsers.

Let’s Bulletproof It

We can add a simple property to the anchor tag so that when someone resizes their text, it does not poke out from under the image:

ul#nav li a {
 background: #FFFF99;
 display: block;
 height: 30px;
 overflow: hidden; /*Added for bulletproofing*/
 padding: 0 5px;
 position: relative;
 width: 115px;

Check out the final example.

More Bulletproofing

If you wanted the text to resize gracefully when images are disabled, I suppose you could set your height in ems. Then you would just need to build some extra blank space into your image, so that it would work when images are enabled.

So hopefully that helps out my co-worker. I think it potentially solves a pretty big problem with CSS image navigation with images disabled.