Techy Treasures #5: Fudging CSS Counters in Internet Explorer

Chocolate IE Fudge. Mmm mmm!

Have a look at this example, of an ordered list with styled numbering: http://www.sitepoint.com/examples/css-counters/

The list-item numbering in that example is implemented using CSS Counters, a feature of CSS2 that allows you to count the instances of a particular element (or group of elements identified by selector), and then use pseudo-elements to display the numbers. In its simplest incarnation it can be used to replace the numbering of an ordered list, as in the example above, which adds a little extra styling to the numbering just to illustrate that it’s happening. As it gets more complex it can be used to replace the deprecated start attribute, or to apply numbering systems that are more sophisticated than native list numbering supports, for example, numbering things like 1.1, 1.2 and so on.

The CSS for that example looks like this:

#css-counters-test
{
	counter-reset: step;
}
#css-counters-test li::before
{
	counter-increment: step;
	content: counter(step) ": ";
	color: green;
	font-weight: bold;
}

Now a problem with its wider uptake is that, as is so often the case, it isn’t supported in Internet Explorer; not even IE8. However, by using a proprietary feature of IE’s CSS implementation, we can in fact implement counters after all. How? By using the much-vaunted but seldom-used expression property.

The main problem with expression, and superficially the reason why it’s no use for generated content, is that it re-evaluates continually — although it can be used to modify the HTML of an element, if we allow it to continually re-evaluate then we’ll be continually changing the HTML. No good at all. But … we can use expressions in a way that avoids re-evaluation, by using a one-time use expression, invented by Jason Davis.

Have a look at the example page in IE and you’ll see that the numbering is working there too, and that’s done with the following code:

#css-counters-test li
{
	zoom: expression(
		  (typeof step == "undefined" ? step = 1 : step++), 
		  this.innerHTML = (typeof this.processed == "undefined" 
				? ("<span class=before>" + step + ": </span>") 
				: "") 
			+ this.innerHTML, 
		  this.processed = true, 
		  this.runtimeStyle.zoom = "1"
		  );
}

#css-counters-test li span.before
{
	color: darkblue;
	font-weight: bold;
}

The theory behind the one-time use expression is simple: an expression can take any number of statements, but only the last one is returned. So in the final statement we override the expression property by writing to runtimeStyle (which is a writeable version of currentStyle, IE’s computed-style property) and that removes the expression property and thereby prevents it from re-evaluating — it doesn’t re-evaluate because it isn’t there anymore!

(Actually, for reasons I haven’t been able to fathom, each of these expressions in fact evaluates twice, and that’s why I’ve used a processed property to prevent the number being written in twice (if I had to guess, I’d say that IE is evaluating the open tag and the close tag for each instance of the selector; but that’s just a guess). But anyway, a simple caveat like that is not a problem to add. The use of the zoom property here is arbitrary — we can use any CSS property at all, as long as IE supports it; I’ve chosen zoom so that I can add a default value at the end which doesn’t otherwise affect the element’s appearance. Another good choice might be height with the value auto.)

So there we have it. While a normal expression, which evaluates continually, is no use for modifying HTML, what we have here can be used for that, and can therefore be used to implement CSS counters!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • philip

    From what I understand, IE8 will no longer support the proprietary CSS expression filter.

    http://blogs.msdn.com/ie/archive/2009/03/12/site-compatibility-and-ie8.aspx

    What isn’t clear to me is if the IE8 mode determines CSS expression’s availability, or if CSS expression is not available in any mode. I think it’s the latter.

  • http://autisticcuckoo.net/ AutisticCuckoo

    Doesn’t IE8 support generated content?! I thought Microsoft promised 100% CSS2.1 compliance with this version.

    Or is it just the CSS3-style syntax (::before) instead of the CSS2.1 equivalent (:before) that it doesn’t support?

  • Philippe

    I was going to note the same as AutisticCuckoo. As far as I can tell, IE 8 does support counters() correctly, but in your example, it won’t show anything, as it doesn’t understand the css3 notation of ::before, only the css2.1 single column notation (:before).

  • http://navin.biz Navin

    This is helpfull. Thanks.

  • ionutzp

    For the IE example, every time a li is parsed in the document, the expression is evaluated and step increments?

  • http://www.brothercake.com/ brothercake

    @Tommy – I thought so too, but it doesn’t appear to work. Actually I hadn’t thought to try the different syntax; lemme try that and I’ll post again.

    @ionutzp – yes exactly.

  • Michael

    I thought IE8 got rid of expressions.

    http://blogs.msdn.com/ie/archive/2009/03/12/site-compatibility-and-ie8.aspx

    Looks like it did. I guess that wraps it up for this article!

  • http://www.brothercake.com/ brothercake

    Nope no joy. Generated content isn’t working in IE8.

  • posaune02

    It doesn’t sound like this will work in IE8 Strict mode as CSS expressions have been removed. As I understand it, they will continue to work in IE8 while in Compatibility mode (IE 5, 7, EmulateIE7, and EmulateIE8 quirks mode).

    http://blogs.msdn.com/ie/archive/2008/10/16/ending-expressions.aspx
    http://blogs.msdn.com/ie/archive/2009/03/12/site-compatibility-and-ie8.aspx

  • http://www.brothercake.com/ brothercake

    Yeah you’re right .. I hadn’t noticed because compatibility mode is enabled by default for local intranet pages (and of course I tested it on my local network).

    How typical of microsoft .. they remove support for a hack without fixing the reason for needing it!

  • http://www.brothercake.com/ brothercake

    @Philippe – I tried it using the CSS2 notation, and it still didn’t work. Can you show me an example of working counters in IE8?

    @Michael – not at all :-P It’s still a valid and useful technique for IE7 and IE6 (and IE8 in compatibility mode)

  • http://simon.html5.org/ zcorpan

    http://software.hixie.ch/utilities/js/live-dom-viewer/saved/54 – works for me in IE8 although the numbers are 1, 4, 6, 8, 10, 12 instead of 1..6. Clearly that’s not correct.

    Also, it’s silly of them to not support the double-colon syntax — I filed a bug about that during the IE7 beta and the IE8 beta but it was rejected both times. Oh well.

  • http://simon.html5.org/ zcorpan

    (…especially considering that double-colon syntax works in IE6!)

  • http://l-c-n.com phiw13

    @Philippe – I tried it using the CSS2 notation, and it still didn’t work. Can you show me an example of working counters in IE8?

    These 2 test cases are supposed to work correctly:
    http://dev.l-c-n.com/css2/counters-Ordered_list.html
    http://dev.l-c-n.com/css2/counters-2.html
    Mind you, check with IE 8 release. Right now, I only have Win 7 with a slightly outdated build; the numbering goes wrong. But the release build should work fine.

    Note that Opera, up to 10alpha is really wrong, when using counters() and there are multiple lists on the page, like my second test case.