Using basic CSS there are various ways to replace text with images, but most of these methods have drawbacks. These drawbacks become even more apparent when you are using image replacement in links (such as in navigation items). A primary reason for using image replacement is to aid in accessibility, but many of the common ways to replace text have accessibility issues. In an effort to choose one of these methods as our company's standard, I evaluated the most common image replacement techniques while creating a site's primary navigation. I thought it might be informative to show the process involved in choosing the option I believe to have the least serious drawbacks.
This may get a little complicated, so I'll first start with some simple examples of image replacement.
Replacement Method #1
Let's start with this code:
<h1><span>About Us</span></h1>
When this header is rendered, what we'd like to see happen is the text 'About Us' disappear and be replaced with a nice image we've made. The easiest way to do this would be to apply a background image to the h1 tag and set the span tag to not display.
h1 {
background-image: url(images/about_us.gif);
width: 200px;
height: 100px;
}
h1 span {
display: none;
}
Unfortunately, setting display to 'none' causes most screen-readers to ignore the text, so nothing gets read at all. In addition, if the user turns off images in their browser but leaves CSS on (a common thing to do on slow connections or handheld devices), the header effectively ceases to exist.
Replacement Method #2
Using the same HTML, this rather serious drawback can be overcome by modifying the CSS a bit.
h1 {
background-image: url(images/about_us.gif);
width: 200px;
height: 100px;
}
h1 span {
display: block;
overflow: hidden;
height: 0;
}
Now the span is set to display block, but is effectively hidden from visual browsers by setting the height to '0' and making sure the overflow is hidden. The background image still shows clearly though and in screen-readers, the text will still be read. Unfortunately, the header still ceases to exist for all intents and purposes if images are disabled but CSS left enabled.
Replacement Method #3
Yet another method removes the need for the span scaffolding altogether, leaving our HTML happily simple and semantic.
h1 {
background-image: url(images/about_us.gif);
width: 200px;
height: 100px;
text-indent: -9999px;
}
Here the background image is still applied to the h1 tag as before, but the text is cleverly removed from view in visual browsers by indenting the text negatively a ridiculous amount. In visual browsers the image display normally but the text is moved far off the screen (probably about 10 feet or so to your left), effectively hiding it. However, screen-readers will still read the text normally. Unfortunately this method, while simple and elegant, still suffers the same drawback as the previous method -- it renders the header tag useless if images are disabled while CSS is still being used. The negative text-indentation trick also has another drawback when links are being used, but I'll get to that later.
Replacement Method #4
The final method of image replacement suffers none of the drawbacks seen in these previous methods. Images display normally in visual browsers with the text hidden, screen-readers read the text normally and the header still renders if images are disabled (whether CSS is used or not). We start with this rather counter-intuitive HTML.
<h1><span></span>About Us</h1>
We're back to using a span tag, but in this HTML, the text is actually outside of it. Our CSS gets a little trickier.
h1 {
width: 200px;
height: 100px;
position: relative;
}
h1 span {
background-image: url(images/about_us.gif);
width: 200px;
height: 100px;
position: absolute;
top: 0;
left: 0;
}
What's going on here is the h1 and the span contained within are both being set to the same height and width. The relative and absolute positioning of the tags allows us to place (in layer terms) the span on top of the h1. Effectively, the text within the h1 is hidden behind the background image of the span. The text will be read normally by screen-readers and turning off images will simply reveal the text underneath. The only drawback to this option is that the width of the text underneath has to be equal to or less than the width of the image replacing it. Otherwise, parts of the text will hang out from underneath the image like feet sticking out from a blanket that's too short. However, in most cases this will not be a problem and in those cases where it is, the size of the text could be manipulated through CSS. If a user bumps their text size from within their browser too high, this will obviously start to become more worrisome -- but this seems to be the least offensive of the drawbacks from all of these methods.
Now lets see what happens when we start to use these techniques within a site's primary navigation where our images need to be anchors. Below is what we're trying to create should look like. Four navigation buttons with dark blue roll-over states.
To achieve this goal, we'll need to create four images. We could make eight (an on and off state for each of the four menu items), but rather than be forced to preload images let's just put the on and off state right into the same image. So here's what the image for the first nav item would look like.
But which replacement method to use? When originally building and testing this navigation, I rejected Method #1 straight-away since it offers no real accessibility benefits. So I started with Method #2. It worked fairly well, but I was unhappy with having to use span tags and the fact that the site becomes unusable if images are disabled (with CSS enabled). Further research led to me to Method #3, which seemed like a big winner. Clean HTML with no span tags and a nifty negative indentation trick -- what's not to love? Sure, it still has that problem with the navigation becoming unusable when images are disabled, but so did the first two methods.
I was content to leave the navigation like this until I started testing it in Firefox. Many browsers (like Firefox) outline links when you click on them or use other methods of link selection (like using Tab in keyboard navigation). What I expected was something that looked like this:
When tabbing over to or clicking on the 'Mission' button, it gets a subtle dashed outline. What I found though using Method #3 was something like this:
In most browsers, the highlighted outline stretched all the way from the nav button to the edge of the browser window (and, in theory, 9000 or so pixels more to the left). This really wasn't acceptable. The problem didn't show up in IE, but who really uses that anyway?
Further research led me to Method #4, which I started playing around with in an attempt to get it to work with anchors. Following the example from above, the HTML for the complete navigation would look something like what I have below. The text and span tags go within our primary tag (here, a list item). The anchors go around both the text and span.
<div id="navigation">
<ul>
<li id="navMission"><a href="#"><span></span>Mission</a></li>
<li id="navNews"><a href="#"><span></span>News</a></li>
<li id="navDonors"><a href="#"><span></span>Donors</a></li>
<li id="navStaff"><a href="#"><span></span>Staff</a></li>
</ul>
</div>
The CSS gets a bit complicated, but I've included the important parts below (for the sake of brevity, I've only included the CSS for the first nav item).
#navigation ul li {
float: left;
display: block;
height: 21px;
position: relative;
}
#navigation a:link,
#navigation a:active,
#navigation a:visited,
#navigation a:hover {
display: block;
height: 21px;
line-height: 21px;
font-size: 9px;
}
#navigation span {
position: absolute;
top: 0;
left: 0;
height: 21px;
}
#navMission {
width: 67px;
}
#navMission span {
background: url(images/nav_mission.gif) no-repeat 0px 0px;
width: 67px;
}
#navMission a:link,
#navMission a:active,
#navMission a:visited,
#navMission a:hover {
width: 67px;
}
#navMission a:hover span,
#navMission a.selected:link span,
#navMission a.selected:visited span,
#navMission a.selected:active span,
#navMission a.selected:hover span {
background: url(images/nav_mission.gif) no-repeat 0px -21px;
}
Like in our previous example, what's happening is the text and the span are being set to the same height and width, and the background-image of the span is being placed on top of the text, effectively hiding it from view like two stacked notecards.
The anchor tag is being applied to both of them and a hover state repositions the background image of the span to show the dark blue "active" state when moused over. As long as the text underneath isn't too long or sized too large, in most cases this should work fine. Further, the links still work when you disable images whether CSS is enabled (as shown below) or not.
Currently this seems to be the most accessible way to do image replacement. At least until things like the CSS3 content property (which replaces the contents of an element with something else) are supported in the majority of browsers. Since this seems unlikely in the near future, we'll likely be relying on tricky work-arounds and clever hacks for quite some time.
Below you can see the fully functioning navigation. You can turn off images and CSS in your browser and see how it looks for yourself. If you so desire, a quick peek at the source code of this page should reveal all the HTML and CSS involved. If you see a problem with this method or know of a more elegant solution, please leave a comment.
Comments
Wed, 03.09.2008 09:53
FF messages
Sat, 02.08.2008 08:49
Thanks a lot for the help! This technique is really nice.
Wed, 16.07.2008 04:20
Nice article about placing and choosing right matter while desiging a web page.
Sat, 21.06.2008 14:10
very nice
Tue, 03.06.2008 05:13
Hi, Regarding Method #3: You can get rid of the long dashed focus border that shows in Firefox by adding 'overflow: [...]
Tue, 29.04.2008 17:45
Thank you for the assistance. It worked perfectly.
Mon, 31.03.2008 09:38
Ditto what Anna said. Each time a Project Mgr or a biz owner asks me, "when are we doing user testing"? They are [...]
Thu, 27.03.2008 16:35
Your points on the idea that you're testing a site, not the user, are well taken. But I think "user testing" can be [...]