An accessible image replacement method using pseudo-elements and generated-content. This method works with images and/or CSS off, with semi-transparent images, doesn’t hide text from screen-readers or search engines, and provides fallback for IE 6 and IE 7.

Known support: Firefox 1.5+, Safari 3+, Chrome 3+, Opera 9+, IE 8+

What’s wrong with current methods?

The two most widely used image replacement techniques are the Gilder/Levin Method and the Phark Method. Both have different flaws.

The Gilder/Levin Method requires the addition of presentational HTML (an empty span) and doesn’t work with transparent images as the default text shows through. The Phark Method uses a negative text-indent to hide the text and so it is not visible when CSS is on and images are off.

Resurrecting the NIR method

Using pseudo-elements and generated-content as an image replacement technique isn’t a new idea. It was proposed and demonstrated by Paul Nash back in 2006. This is the Nash Image Replacement method.

<h1 class="nir">[content]</h1>
.nir {
height: 100px; /* height of replacement image */
padding: 0;
margin: 0;
overflow: hidden;
}

.nir:before {
content: url(image.gif);
display: block;
}

The height value is equal to that of the replacement image. Setting overflow:hidden ensures that the original content is not visible on screen when the image is loaded. The replacement image is inserted as generated content in the :before pseudo-element which is set to behave like a block element in order to push the element’s original content down.

What about IE 6 and IE 7?

Neither browser supports :before; if you need to support them you’ll have to rely on the Phark method. This can be done using conditional comments or safe IE6/7 hacks to serve alternative styles to legacy versions of IE .

<!--[if lte IE 7]>
<style>
.nir
{
height: 100px;
padding: 0;
margin: 0;
overflow: hidden;
text-indent: -9000px;
background: url(image.gif) no-repeat 0 0;
}
</style>
<![endif]-->

Using the NIR method allows you to keep your HTML semantic and deliver improved accessibility to users of modern browsers. The Phark Method can then be served to IE 6 and IE 7.

Improving the NIR method

The first problem with NIR is that if images are disabled all browsers leave whitespace above the element’s content. Opera 10.5 even displays the text string “image”! If the height of the element is small enough this whitespace causes the element’s content to overflow and be partially or completely hidden when images are disabled.

Another consideration is what happens if an image doesn’t exist or fails to load. Safari and Chrome will display a “missing image” icon that cannot be removed. Once again, this can cause the element’s content to overflow and become partially or completely hidden to users.

A more robust version of the NIR method is the following modification:

.nir {
   height: 100px; /* height of replacement image */
   width: 400px; /* width of replacement image */
   padding: 0;
   margin: 0;
   overflow: hidden;
}

.nir:before {
   content: url(image.gif);
   display: inline-block;
   font-size: 0;
   line-height: 0;
}

Setting font-size and line-height to 0 avoids the whitespace problems in all browsers. Setting the element’s width equal to that of the replacement image and getting the pseudo-element to act as an inline-block helps minimise the problems in webkit browsers should an image fail to load.

Ideally browsers would avoid displaying anything in a pseudo-element when its generated-content image fails to load. If that were the case, the original NIR method would be all that is needed.

What about using sprites?

One of the most common uses of image replacement is for navigation. This often involves using a large sprite with :hover and :active states as a background image. It turns out that using sprites is not a problem for modern browsers. When using the modified-NIR method the sprite is included as a generated-content image that is positioned using negative margins.

This is an example that rebuilds the right-hand category navigation from Web Designer Wall using a sprite and the modified-NIR method.

<ul id="nav">
<li id="nav-item-1"><a href="#non">Tutorials</a></li>
<li id="nav-item-2"><a href="#non">Trends</a></li>
<li id="nav-item-3"><a href="#non">General</a></li>
</ul>
/* modified-NIR */

#nav a {
  display: block;
  width: 225px;
  height: 46px;
  overflow: hidden;
}

#nav a:before {
   content:url(sprite.png);
   display:-moz-inline-box; /* for Firefox 1.5 & 2 */
   display:inline-block;
   font-size:0;
   line-height:0;
}

/* repositioning the sprite */

#nav-item-1 a:hover:before,
#nav-item-1 a:focus:before,
#nav-item-1 a:active:before {margin:-46px 0 0;}

#nav-item-2 a:before        {margin:-92px 0 0;}
#nav-item-2 a:hover:before,
#nav-item-2 a:focus:before,
#nav-item-2 a:active:before {margin:-138px 0 0;}

#nav-item-3 a:before        {margin:-184px 0 0;}
#nav-item-3 a:hover:before,
#nav-item-3 a:focus:before,
#nav-item-3 a:active:before {margin:-230px 0 0;}

/* :hover hack for IE8 if no a:hover styles declared */
#nav a:hover {cursor:pointer;}

For some reason IE8 refuses to reposition the image when the mouse is over the link unless a style is declared for a:hover. In most cases you will have declared a:hover styles for the basic links on your webpage, and this is enough. But it is worth being aware of this IE8 behaviour.

The addition of display:-moz-inline-box; is required to reposition the sprite in versions of Firefox prior to Firefox 3.0. They are very rare browsers but I’ve included it in case that level of legacy support is needed.

If you want image replacement in IE 6 and IE 7 the following additional styles can be served to those browsers using conditional comments.

/* Phark IR method */

#nav a {
text-indent: -9000px;
background: url(sprite.png) no-repeat;
}

/* repositioning the sprite */

#nav-item-1 a:hover,
#nav-item-1 a:active
{ background-position: 0 -46px; }

#nav-item-2 a { background-position: 0 -92px; }
#nav-item-2 a:hover,
#nav-item-2 a:hover
{ background-position: 0 -138px; }

#nav-item-3 a { background-position: 0 -184px; }
#nav-item-3 a:hover,
#nav-item-3 a:active
{ background-position: 0 -230px; }

/* hack for IE6 */
#nav a:hover { margin: 0; }

The changes are fairly simple. But IE 6 applies the margins declared for a:hover:before to a:hover and so they need to be reset in the styles served to IE 6.

See the modified-NIR (using sprites) demo.