Read CSS: The Definitive Guide, 3rd Edition Online
Authors: Eric A. Meyer
Tags: #COMPUTERS / Web / Page Design
We're all familiar with
counters; for example, the markers of the list items in ordered lists are counters.
In CSS1, there was no way to affect them, largely because there was no need: HTML
defined its own counting behaviors for ordered lists, and that was that. With the
rise of XML, it's now important to provide a method by which counters can be defined.
CSS2 was not content to simply provide for the kind of simple counting found in HTML,
however. Two properties and twocontent
values
make it possible to define almost any counting format, including subsection counters
employing multiple styles, such as "VII.2.c."
The basis of
creating counters is the ability to set both the starting point for a counter and
to increment it by some amount. The former is handled by the propertycounter-reset
.
counter-reset
[none
|inherit
User agent-dependent
All elements
No
As specified
A counter identifier is simply a label created by the author. For
example, you might name your subsection countersubsection
,subsec
,ss
, orbob
. The
simple act of resetting (or incrementing)
an identifier
is sufficient to call it into being. In the following rule, the counterchapter
is defined as it is
reset:
h1 {counter-reset: chapter;}
By
default, a counter is reset to zero. If you want to reset to a different number,
you can declare the number following the
identifier:
h1#ch4 {counter-reset: Chapter 4;}
You
can also reset multiple identifiers all at once in identifier-integer pairs. If
you leave out an integer, then it defaults to
zero:
h1 {counter-reset: Chapter 4 section -1 subsec figure 1;}
/* 'subsec' is reset to 0 */
As
you can see from the previous example, negative values are permitted. It would be
perfectly legal to set a counter to-32768
and
count up from there.
CSS does not define what user agents should do with negative counter values
in nonnumeric counting styles. For example, there is no defined behavior for
what to do if a counter's value is-5
but
its display style isupper-alpha
.
To count up, you'll need a property to indicate that an element
increments a counter. Otherwise, the counter would remain at whatever value it was
given with acounter-reset
declaration. The
property in question is, not surprisingly,counter-increment
.
counter-increment
[none
|inherit
User agent-dependent
All elements
No
As specified
Likecounter-reset
,counter-increment
accepts identifier-integer pairs,
and the integer portion of these pairs can be zero or negative as well as
positive. The difference is that if an integer is omitted from a pair incounter-increment
, it defaults to 1, not
0.
As an example, here's how a user agent might define counters to
recreate the traditional 1, 2, 3 counting of ordered
lists:
ol {counter-reset: ordered;} /* defaults to 0 */
ol li {counter-increment: ordered;} /* defaults to 1 */
On
the other hand, an author might want to count backward from zero so that the list
items use a rising negative system. This would require only a small
edit:
ol {counter-reset: ordered;} /* defaults to 0 */
ol li {counter-increment: ordered -1;}
The
counting of lists would then be -1, -2, -3 and so on. If you replaced the integer-1
with-2
, then lists would count -2, -4, -6 and so on.
To actually display the counters, though, you need
to use thecontent
property in conjunction with
one of the counter-related values. To see how this works, let's use an XML-based
ordered list like
this:
- First item
- Item two
- The third item
By
applying the following rules to XML employing this structure, you would get the
result shown in
Figure
12-22
:
list[type="ordered"] {counter-reset: ordered;} /* defaults to 0 */
list[type="ordered"] item {display: block;}
list[type="ordered"] item:before {counter-increment: ordered;
content: counter(ordered) ". "; margin: 0.25em 0;}
Figure 12-22. Counting the items
Note that the generated content is, as usual, placed as inline content
at the beginning of the associated element. Thus, the effect is similar to an HTML
list withlist-style-position
:inside;
declared.
Note also that theitem
elements are ordinary elements
generating block-level boxes, which means that counters are not restricted only to
elements with adisplay
oflist-item
. In fact, any element can make use of a
counter. Consider the following
rules:
h1:before {counter-reset: section subsec;
counter-increment: chapter;
content: counter(chapter) ". ";}
h2:before {counter-reset: subsec;
counter-increment: section;
content: counter(chapter )"." counter(section) ". ";}
h3:before {counter-increment: subsec;
content: counter(chapter) "." counter(section) "." counter(subsec) ". ";}
These
rules would have the effect shown in
Figure
12-23
.
Figure 12-23. Adding counters to headings
Figure 12-23
illustrates some
important points about counter resetting
and
incrementing.
Notice how
theh1
element uses the counterchapter
, which defaults to zero and has a "1." before
the element's text. When a counter is incremented and used by the same element,
the incrementation happens
before
the counter is displayed.
In a similar way, if a counter is reset and shown in the same element, the reset
happens before the counter is displayed.
Consider:
h1:before, h2:before, h3:before {
content: counter(chapter) "." counter(section) "." counter(subsec) ". ";}
h1 {counter-reset: section subsec;
counter-increment: chapter;}
The
firsth1
element in the document would be
preceded by the text "1.0.0. " because the counterssection
andsubsec
were reset,
but not incremented. This means that if you want the first displayed instance of
an incremented counter to be 0, then you need to reset that counter to-1
, as
follows:
body {counter-reset: chapter -1;}
h1:before {counter-increment: chapter; content: counter(chapter) ". ";}
You
can do some interesting things with counters. Consider the following
XML:
PRINT "Hello world!" REM This is what the kids are calling a "comment" GOTO 10
You
can recreate the traditional format of a BASIC program listing with the following
rules:
code[type="BASIC"] {counter-reset: linenum; font-family: monospace;}
code[type="BASIC"] line {display: block;}
code[type="BASIC"] line:before {counter-increment: linenum;
content: counter(linenum 10) ": ";}
It's
also possible to define a list style for each counter as part of thecounter( )
format. You can do this by adding a
comma-separatedlist-style-type
keyword after
the counter's identifier. The following modification of the heading-counter
example is illustrated in
Figure
12-24
:
h1:before {counter-reset: section subsec;
counter-increment: chapter;
content: counter(chapter,upper-alpha) ". ";}
h2:before {counter-reset: subsec;
counter-increment: section;
content: counter(chapter,upper-alpha)"." counter(section) ". ";}
h3:before {counter-increment: subsec;
content: counter(chapter,upper-alpha) "." counter(section) "."
counter(subsec,lower-roman) ". ";}
Figure 12-24. Changing counter styles
Notice that the countersection
was
not given a style keyword, so it defaulted to the decimal counting style. You can
even set counters to use the stylesdisc
,circle
,square
, andnone
if you so
desire.
One interesting point to note is that elements with adisplay
ofnone
do
not increment counters, even if the rule seems to indicate otherwise. In contrast,
elements with avisibility
ofhidden
do increment
counters:
.suppress {counter-increment: cntr; display: none;}
/* 'cntr' is NOT incremented */
.invisible {counter-increment: cntr; visibility: hidden;}
/* 'cntr' IS incremented */
So far, we've seen how to string
multiple counters together to create section-and-subsection counting. Often, this
is something authors desire for nested ordered lists as well, but it would quickly
become clumsy to try to create enough counters to cover deep nesting levels. Just
to get it working for five-level-deep nested lists would require a bunch of rules
like
this:
ol ol ol ol ol li:before {counter-increment: ord1 ord2 ord3 ord4 ord5;
content: counter(ord1) "." counter(ord2) "." counter(ord3) "."
counter(ord4) "." counter(ord5) ".";}
Imagine
writing enough rules to cover nesting up to 50 levels! (I'm not saying you should
nest ordered lists 50 deep. Just follow along for the
moment.)
Fortunately, CSS2.x described the concept of
scope
when it comes to counters. Stated simply, every level
of nesting creates a new scope for any given counter. Scope is what makes it
possible for the following rules to cover nested-list counting in the usual HTML
way:
ol {counter-reset: ordered;}
ol li:before {counter-increment: ordered;
content: counter(ordered) ". ";}
These
rules will all make ordered lists, even those nested inside others, start counting
from 1 and increment each item by one—exactly how it's been done in HTML from the
beginning.
This works because a new instance of the counterordered
is created at each level of nesting. So, for
the first ordered list, an instance ofordered
is created. Then, for every list nested inside the first one, another new instance
is created, and the counting starts anew with each list.
However, you
want ordered lists to count so that each level of nesting creates a new counter
appended to the old: 1, 1.1, 1.2, 1.2.1, 1.2.2, 1.3, 2, 2.1, and so on. This can't
be done withcounter( )
, but it
can
be done withcounters(
. What a difference an "s" makes.
)
To create the
nested-counter style shown in
Figure
12-25
, you need these
rules:
ol {counter-reset: ordered;}
ol li:before {counter-increment: ordered;
content: counters(ordered,".") " - ";}
Figure 12-25. Nested counters
Basically, the keywordcounters(ordered,".")
displays theordered
counter from each scope with a period appended, and strings
together all of the scoped counters for a given element. Thus, an item in a
third-level-nested list would be prefaced with theordered
value for the outermost list's scope, the scope of the list
between the outer and current list, and the current list's scope, with each of
those followed by a period. The rest of thecontent
value causes a space, hyphen, and space to be added after all
of those counters.
As withcounter(
, you can define a list style for nested counters, but the same style
)
applies to all of the counters. Thus, if you changed your previous CSS to read as
follows, the list items in
Figure
12-25
would all use lowercase letters for the counters instead of
numbers:
ol li:before {counter-increment: ordered;
content: counters(ordered,".",lower-alpha) ": ";}