June 5, 2004
Made a CSS counters test page, and linked to it on the CSS Alacrity page. Counters are actually pretty hard to understand (well, if you read the spec and expect it to be a tutorial), but I finally managed, so I'll try to explain how I did the CSS counters test page in simpler terms than how the spec explains counters. Note that as of right now, counters are only supported by Opera (as far as I know; feel free to correct me).
A counter is created as soon as it is referred to, and is included in the content
property, in either :before
or :after
pseudo-elements (CSS3 will allow you to use content
with any selector, not limited to :before
or :after
). The function for counters is counter()
and like everything in the content
property (be it a string, the attr()
function, url()
...) it is concatenated with the rest with a space. counter()
takes an identifier (and optionally any value from list-style-type
, separated with a comma) as its argument. Put another way, you name your counters as you see fit. So, first we'll want to generated the string "Chapter " followed by the chapter number followed by the string ": " before an <h1>
element:
h1:before {
content: "Chapter " counter(chapter) ": ";
}
Actually, this won't do much good; the only thing it'll do is prefix "Chapter 0: " to every <h1>
element. We want to increment it by 1 every time another <h1>
is encountered. This is done with the counter-increment
property. This property takes an identifier plus an optional integer as its value (or even several of the two). The integer specifies how much the counter is incremented each time the element is encountered, and can even be negative. The default value is 1 however, which fits our need just fine, so we can omit the integer in this case. Our CSS now looks like this:
h1:before {
content: "Chapter " counter(chapter) ": ";
counter-increment: chapter;
}
Note that the counter is incremented before it's prefixed to <h1>
, even though content
appears before counter-increment
in the source.
That takes care of our most important headings. Now we want to prefix our <h2>
s with "Section " followed by the current state of the chapter
counter, followed by a dot, followed by a new counter we'll create called counter(section)
. Remember that counter(chapter)
isn't incremented by anything else than <h1>
, so it'll stay the same throughout all <h2>
s until they're interrupted by another <h1>
:
h2:before {
content: "Section " counter(chapter) "." counter(section) ": ";
counter-increment: section;
}
Uh-oh! What happens to the <h2>
s interrupted by an <h1>
? You're absolutely right; they'll just keep counting, which is not quite what we want. The counter-reset
property will take care of that little problem. It has the exact same syntax as counter-increment
, except that the optional integer specifies to what value the counter is set when being reset (the default is 0, which is what we want, so we omit the integer once again). So for every <h1>
encountered, we want counter(section)
reset. Here's the first CSS rule, modified:
h1:before {
content: "Chapter " counter(chapter) ": ";
counter-increment: chapter;
counter-reset: section;
}
This means that we must also reset the counter for our <h3>
elements (which we'll call counter(sub-section)
) whenever an <h2>
element is encountered. Our second CSS rule is thusly modified:
h2:before {
content: "Section " counter(chapter) "." counter(section) ": ";
counter-increment: section;
counter-reset: sub-section;
}
So as you can see, counters are pretty nifty. Realize that counter-increment
doesn't need to be in the same rule block as the counter()
it increments. This allows for cool effects, such as, say, counting the number of paragraphs on a page and displaying the results at the bottom of the page. (Though a silly idea, it's a good way to understand counters.) Create a dummy <div>
at the bottom of the page, called, say, <div id="para_counter">
. CSS is as follows:
p {
counter-increment: paragraphs;
}
div#para_counter:before {
content: "There are " counter(paragraphs) " paragraphs on this page.";
}
Incidentally, I now use counters to count the number of quotes I have on the Quotes page.