CSS sprites are now almost a decade old. The core concept of the technique – creating a “panel” of images that are displayed as a background in links and moved to show different icons – is enshrined in thousands of web pages and dozens of frameworks.

As a concept, CSS sprites are very powerful: one image loaded for all buttons on a site cuts page loading times by eliminating multiple server requests for separate files. But practically, I’ve always had several issues with them:

  1. Sprite panels are complex to make, requiring at least two different images for each icon state.
  2. There is no alt attribute for background images: so if the sprite does not load, there is nothing to click on in the navigation.
  3. Similarly, without an alt attribute CSS Sprites are not .
  4. Without alternative text, search engines lack an understanding of the purpose of links.
  5. CSS sprites cannot be animated, unless one tried to turn an animated GIF into a sprite (:: shudder ::)
  6. Making two versions of each icon doubles the panel size, increasing load time and complicating the CSS.
  7. The CSS to make a sprite work was somewhat complex, requiring multiple sets of coordinates.

In considering the redesign of this site (currently under development) I very much wanted to update the idea of CSS sprites for the 21st century, eliminating the drawbacks of the traditional approach. I call my solution “CSS3 Sprites”.  You can see an oversized example in the header image for this article.

The process to create a CSS3 sprite panel is just nine steps:

  1. Make a sprite panel as you would traditionally, but the with icons drawn in a single state only (a monochrome theme will probably work best.). Optional: make the sprite panel twice as large as what will be actually displayed on the page.
  2. Export the panel. If you’ve taken the optional step above, export the panel in two sizes: one at the final size of the collective icons as displayed by default on the page, say 150px × 150px, and another twice as large (ie. 300px by 300px). For this example I’ll call the first image temp-sprite-panel and the second sprite-panel. (I’d also recommend that you export the image(s) as transparent PNG files for the most flexibility).
  3. Create an <nav> element for the navigation and place the temp-sprite-panel.png file as an img tag inside links, as you normally would. Include alt attribute values that are appropriate for the icon that will be visible at that point.
  4. Attach a class to the unordered list. The class will be used for general shared properties of all uses of the sprite panel.

At this stage, our HTML code should look something like this:

<nav class=sprites>
	a href="#">
		<img src="temp-sprite-panel.png" alt="RSS">
	</a>
	<a href="#">
		<img src="temp-sprite-panel.png" alt="About">
	</a>
	<a href="#">
		<img src="temp-sprite-panel.png" alt="Book Mode">
	</a>
</nav>

Right now, each list item link contains a copy of the entire visible sprite panel image, with the list items positioned one underneath the other. We are going to put the list items side by side while reducing the opacity of the image used in each link, to create a default “inactive” state for every icon. In our CSS:

nav.sprites a img {
	position: absolute;
	opacity: 0.4;
	width: 150px;
	height: 76px;
}

(Note that I've placed the size of the temp-sprite-panel.png file in the CSS).

Next, we will visually restrict each image to show only the appropriate icon by using the clip property. clip requires that its element be positioned absolutely, which is why we applied it in the CSS above. We’ll apply the CSS by referencing the alt attribute of each sprite.

Remember that clip uses offsets from the top and left and edges of the image to determine which portion to show. You may want to open up your sprite panel in PhotoShop to determine these numbers. For our example, they might be something like:

img[alt="RSS"] {
	clip: rect(54px,21px,76px,0px);
}
img[alt="About"] {
	clip: rect(54px,47px,76px,25px);
}
img[alt="Book Mode"] {
	clip: rect(54px,75px,76px,48px);
}

Because clipped images have no “height” per se, we need to set a height on the navigation list. This should be the same as the height of all uses of CSS3 sprite navigation on our site (as the icons are all the same height), so we can put that on our class for our list:

nav.sprites {
	height: 27px;
}

With everything now in place, the last thing we need to do is create the “active” state for each icon, via a hover pseudo-selector:

nav.sprites a:hover img {
	opacity: 1;
}

That’s it! Each sprite is now accessible, as each has its own unique alt value. If you wanted to animate the sprite transition:

nav.sprites a img {
	position: absolute;
	opacity: 0.4;
	width: 150px;
	height: 76px;
	transition: all 0.5s linear;
}

If you want to retain greater image quality during page zooms of the sprite panel on browsers or mobile devices, substitute sprite-panel.png for temp-sprite-panel.png in the HTML, without changing the CSS. The larger image will be shrunk down to the size of the former, smaller panel, then the clip(s) will be applied. When the page zooms, each icon will look sharper.