CSS-Plus
CSS-Plus
Play with web technologies

Use :pseudo-elements as list-style-image alternatives

December 01, 2011
Attention: This article is old and the content is outdated. I've left the article here for historical purposes.
Use :pseudo-elements as list-style-image alternatives

Styling lists with CSS can be a bit of a pain from time to time. If I require an kind of customized bullet, the first CSS property I think of is list-style: none;. I've tried on more than one occasion to use list-style-image but it's given me nothing but trouble when attempting to position it. A popular alternative is to use the image as a background-image of the list item.

li { background: url(images/bullet.png) no-repeat left top; list-style: none; }

This gives us the ability to move the 'list-item' around as we please without problems. Great solution! right?! No, there are three very annoying (however, small) problems with this method:

  • You can't effectively include the list item in a sprite, as you'd run into problems displaying areas of the sprite you wish to remain hidden.
  • An image obviously can't work with ordered lists as the numbered list-style-type would require a different image for each list item. This is possible but not very effective as it would require a lot of images and it wouldn't be ever-expanding - It would have static amount of numbered images.
  • Adds to HTTP requests - this is especially annoying if you've got a couple of different lists all using this method
Those are my two problems I have with the background-image, but I have managed to find a way around it.

Standard background-image method

Before I get started on the way I usually do this, let me show you what I did before and what I think most people do.

The HTML necessary

<ul class="first-example">
	<li>SEO</li>
	<li>Content Management System</li>
	<li>Social Network Integration</li>
	<li>Another list item for emphasis</li>
</ul>

The CSS necessary

.first-example li { background: url(../i/tick.png) no-repeat left 8px; }
.first-example li { border-bottom: 1px solid #eee; font-size: 14px; list-style: none; padding: 10px 0 10px 40px; }
You can see what's going on, no explanation needed. The only thing to note here is the list items contain a background image and that image is a single file separate from the sprite which adds to http requests.

Make use of pseudo elements

Pseudo elements are our friends. They are that extra bit of gas in the tank. When everything looks dull and there is no easy way to do what it is you want to do, look towards pseudo elements.

We could use pseudo elements as the 'bullets'. This would solve the problem of showing more sprite than we'd like since we can set the pseudo-element's width and height and give it the sprite background.

The HTML

The HTML is exactly the same as the first example with. The only difference is the class selector used for us to target.
<ul class="second-example">
	<li>SEO</li>
	<li>Content Management System</li>
	<li>Social Network Integration</li>
	<li>Another list item for emphasis</li>
</ul>
li { border-bottom: 1px solid #eee; font-size: 14px; list-style: none; padding: 10px 0 10px 40px; position: relative; }

ul.second-example li:before { content: ''; background: url(../i/sprite.png) no-repeat left top; height: 20px; width: 20px; position: absolute; left: 0; top: 8px; }

ul.second-example li:nth-child(2n):before { background-position: -21px 0; }

Why exactly are we giving every second example a different list item image? For two reasons:

  • It gives me an excuse to use a sprite in this example
  • We're just retro and hip like that
Le's see what would have happened if we gave the first example the sprite as the background.

List item with the sprite as a background-image

List items with a sprite background image

List item with pseudo element using the sprite as a background image - Preferred method

Pseudo elements acting as list item bullets

Make use of CSS counters

What exactly are CSS counters? A CSS counter is a function that is accepted by the content property of a pseudo element. You can count whatever it is you like. The amount of <section> elements in an article, the amount of <h2> elements, etc. In this case we're going to be counting the amount of <li> elements. This way we can dynamically give the :before/:after pseudo elements a numerical value which will help to number our list items.

The CSS counter property is within the CSS 2.1 spec. So this means that it has support down to IE8. I understand that it won't work in IE7 & IE6, but I don't really care too much — I'd probably just give them the default numerical list-item-type.

Bring on the CSS

Some of CSS following will be the same as the previous CSS, but that's because the list is styled in a similar fashion. li { border-bottom: 1px solid #eee; font-size: 14px; list-style: none; padding: 10px 0 10px 40px; position: relative; } li:first-child { border-top: 1px solid #eee; }

ul.first-example li { content: ''; background: url(../i/tick.png) no-repeat left 8px; }

ol.third-example { counter-reset: li; }

ol.third-example li { position: relative; }

ol.third-example li:before { 
	content: counter(li); 
	counter-increment: li; 
	background: #f692bb; 
	color: #333;
	font: bold 14px/20px sans-serif; 
	height: 20px; 
	text-align: center; 
	width: 20px; 
	position: absolute; 
	left: 0; 
	top: 8px; 
	-webkit-border-radius: 10px;
	   -moz-border-radius: 10px;
	        border-radius: 10px;
	-webkit-box-shadow: inset 0 0 5px #F0498D, 2px 2px 0 rgba(0,0,0,.15);
	   -moz-box-shadow: inset 0 0 5px #F0498D, 2px 2px 0 rgba(0,0,0,.15);
		box-shadow: inset 0 0 5px #F0498D, 2px 2px 0 rgba(0,0,0,.15);
}

Before I continue, thanks paulirish and jon_neal for CSS3 Please - Quite a helpful tool.

Note: the position relative on the list item is very necessary since you'll be absolutely positioning the pseudo elements within each item.

To use the counter() function properly, 3 things need to ne done.

  • The counter of the specific element needs to be reset — Or as I like to think of it, enabled
  • The counter-increment needs to be told what to count, and by how much. (the increment could be by 2, or 10, in this example we're going to leave it on 1 by default)
  • The counter() function needs to be passed into the content property and then told what to count
Ok so first thing to do is to add the counter-reset property to the parent/ancestor container of the item you wish to count. You then give the counter-reset value the element type of the element to count. If you'd like to count the amount of paragraphs in the article element, you could reset the counting by adding the following CSS to your document:
body { counter-reset: p; }
/* Or */
article { counter-reset: p; }
It cascades, so any container will do.

You would then add the counter-increment property to the pseudo element, this tells the pseudo element which element to count and by how much.

li { counter-increment: li 5; }

That example would count in increments of 5 starting on that value, so: 5, 10, 15, etc. If you leave that value out it defaults to 1.

Finally, you would then add the counter() function to the content property of the pseudo element. This literally tells the pseudo element to contain the incremented value.

Conclusion

I find this extremely useful and consider it one of my better CSS tricks. Browser support isn't horrible, but not brilliant either — The inclusion of IE7 would make it brilliant. Never the less, it's completely usable in every day scenarios since I use it fairly often and I give IE7 the deafult numbered styling.

To recap: counter-reset: element, counter-increment: element num, content: counter(element).

Note: I actually make use of the custom bullets via pseudo elements on my unordered list items within my current website design.

Demo