How to use CSS sprites to create custom bullets in HTML

When creating unordered lists (i.e. the ones with bullets and not numbers or letters) in HTML, you can use the list-image CSS property to use an image to replace the default bullets HTML supports.

That’s cool. But, if you are mindful of front-end optimization and decide to use CSS sprites throughout your site to reduce HTTP requests,  the list-image CSS property falls short, because it is intended to use only one perfect, well-tailored bullet image for each bullet style you define. So, if you have ten different types of lists with different image bullets, you will need as many or more separate images to customize your lists. And that is NOT cool.

So, how do you use CSS sprites to create custom bullets?

Example

First, here is the demo of the bulleted list with custom bullets made using CSS Sprites we’ll be developing in this example. A few things to notice:

  • The bullets are made of custom images contained in a CSS sprite.
  • The bullets change on hover from gray to red, just like each link does (something not accomplished by default when only using the a:hover CSS property).

Now, let’s get to it.

What you need:

The HTML

This is the basic HTML we’ll use for this example, which creates a list of links to awesome tropical flavors:

<ul id="my-list">
	<li><a href="#">Mango</a></li>
	<li><a href="#">Banana</a></li>
	<li><a href="#">Watermelon</a></li>
</ul>

The CSS

This is the initial CSS we’ll use in this example. We’ll develop it more as we go along:

#my-list{
	width: 120px;
	padding: 16px;
	border: 1px dotted #CCC;
}

#my-list a{
	display:block;
	padding: 4px 0;
	color:#333;
	text-decoration:none;
}

#my-list a:hover{
	color:#900;
}

#my-list li{
	margin: 0 0 0 12px;
}

The HTML and CSS above generate a list that looks like this:

The custom-bullets sprite image

Ok so, what’s are CSS sprites, again?

In a few words, CSS sprites are basically images that contain a grid of images, like this one from Google. The idea is that instead of loading each image separately, you can load a master image that contains them all, and then use that master image as a background in all HTML elements that need it. This helps reduce the amount of HTTP requests your page makes on load, which then helps reduce loading times. A List Apart has a seminal article on CSS Sprites that, despite being old, will still blow your mind. Also, Christ Coyier from CSS-tricks.com has a cool article with more info on how to use sprites with an example from his own website. Read it.

Here’s the image very simple image sprite we’ll use for this example:

Getting it done:

Let’s further expand the CSS above to make this thing work:

  1. Remove the “bullets” from each li element
#my-list li{
	margin: 0 0 0 12px;
	list-style:none;
}
  1. Remove the left margin and  some the same amount in padding. This helps both make up for the space lost when the bullets were removed and open some space between the border of the li element and the anchor element contained in it
#my-list li{
	list-style:none;
	padding: 0 0 0 12px;
}

At this point, the list should look like this:

  1. Set the sprite as the background for the li element
#my-list li{
	list-style:none;
	padding: 0 0 0 12px;
	background: url(images/sprite.png) -8px 0px;
}
  1. Set the background for the :hover of the li element
#my-list li:hover{

background: url(images/sprite.png) -8px 24px
}

Notice that the list element is now using the sprite image as its background, but it repeats all over the place:

There are two ways to solve this, depending on how your sprite is set up:

  1. a) If your sprite is setup vertically (i.e. if all the images in the sprite are stacked in one column), you can do with just setting the background-repeat to repeat-y at it will do the trick:
#my-list li{
	list-style:none;
	padding: 0 0 0 12px;
	background: url(images/sprite.png) -8px 0px repeat-y;
}

#my-list li:hover{

	background: url(images/sprite.png) -8px 24px repeat-y
}
  1. b) If your sprite is setup horizontally (i.e. if all images in the sprite are distributed across the grid, next to each other, like the one in the Googlee example shown above), you may be better off by setting up background color for the most immediate element contained in the li element (in our case, the <a> element):
#my-list a{
	display:block;
	padding: 4px 0;
	color:#333;
	text-decoration:none;
	background-color:#FFF;
}

Whichever the case, your list should now look like this:

And that’s it! Click here to see the live example »

See you next time!

Be Sociable, Share!

    12 Comments

    1. The power of Facebook… I saw you blogged and tweeted… something apparently for geeks.
      Being a geek, I needed to read, so I found your blog, but not the tweets! Looks like you need some social networking links on this site.
      Nice tutorial! I ran into usage of this with some arrows that rotate on a navigation tree I’m using. It was also for list items which receive different classes, depending on which node is active.

      Reply
      • Hi Scott,

        Thanks for writing! I’m glad you found the post interesting, and hopefully useful.

        I’m constantly thinking up of web dev problems and how to solve them. If you run into one (or if you already have a few) let me know and I can take a stab at it.

        Thanks!

        Reply
    2. Great tip – unfortunately it won’t work for a list item that wraps into more than one line.

      Reply
    3. What is a user changes their font size in their browser? It will break it.

      Reply
      • Hi Joe,

        Good point. I just tested it, and since most major browsers now zoom in the whole page rather than only the text (FF, Chrome, Opera and Safari), it looks fine on those.

        Of course, IE only zooms text by default (Safari allows you to choose that option, if you want to). In such case, the sprite does show more than it should.

        You could estimate how much you think your intended audience would zoom in (by comparing your users’ screen sizes vs your site’s font size) and arrange your sprite accordingly by adding more space between elements. What do you think?

        Reply
    4. I would like to know if something similar can be done to change the bullet image when visited
      as I tried doing it and it didn’t worked

      Let me know

      Reply
    5. Your tutorial works great!

      I hope this tutorial is still being monitored. I tried to customize it using an image sprite that is 12px X 1117px. There are 23 icons stacked vertically with approx 35px space separating each icon from the other. My navigation bar is linear and each bullet will be a different icon. I am pulling my hair out trying to figure out how to do this. I can get the sprite image to show, but when I target an icon, all the bullets will use the same icon and I am unable to use any others I target for each bullet.


      Comment
      E-mail
      Twitter
      Facebook
      LinkedIn

      .options {
      width: 300px;
      padding: 16px;
      border: 1px dotted #CCC;

      }

      .options a {
      display:inline-block;
      padding: 4px 0;
      color:#333;
      text-decoration:none;

      }

      .options li {
      background: url("images/sprites.gif")0 -478px no-repeat;
      height:17px;
      }

      .options li.email a {
      background-position:0 -75px no-repeat;
      }

      .options a:hover{
      color:#900;
      }

      Can you help me out?

      Thanks very much!

      Reply
    6. here’s a fiddle with a slightly different markup:

      Sprite
      Fiddle

      Thanks again!

      Reply
      • Hey Chris,

        Thanks for writing!

        I wasn’t able to get your final fiddle work all the way, since it’s missing the sprite image, but here it goes:

        – The image sprite in your code applies to all the .options li elements (as in your original code in the comments) and that’s good:


        .options li {
        background: url("images/sprites.gif")0 -478px no-repeat; /*This applies to all .options li elements, regardless of their class (.email, .tweet, etc)*/
        height:17px;
        }

        – Now, a custom positioning of the background sprite should be specified for each li element based on its class (.email, .tweet, etc), and not for the a elements inside them (which is what you have in your code):


        /*do this*/
        .options li.email {
        background-position:0 -75px no-repeat;
        }

        /*instead of this:*/
        .options li.email a {
        background-position:0 -75px no-repeat;
        }

        – That being said, you should have a “default” style for each li element based on its class and a :hover style as well, so:


        .options li.email a {
        background-position:0 -75px no-repeat;
        }
        .options li.email:hover a {
        background-position:0px -478px no-repeat;
        }
        .options li.tweet a {
        background-position:0 [number]px no-repeat;
        }
        .options li.tweet:hover a {
        background-position:0px [number]px no-repeat;
        }
        /*..and so on for each remaining li class...*/

        That way, you will style the li elements in such way that the sprite will appear in normal and hover pointing at the right icons based on the li class (.tweet, .email, etc).

        So in the end, you want three things (repeat the number 2 and 3 for each extra li class you have):

        1.- a CSS declaration that adds the background sprite image to all .options li elements
        2.- a CSS declaration that repositions that background sprite for each .options li depending on its class (.options li.email, .options li.tweet, …)
        3.- a CSS declaration that repositions the background sprire for each .options li on HOVER depending on its class

        And that should be it. Let me know how it goes!

        Reply
    7. Thanks for that post:
      By examining your source, i found out the only solution why mine one didn’t work was a simple typo 🙂
      (Though, running it through a validator would have helped, too)

      Reply

    Leave a Comment