CSS3 – Lists and Generated Content

ASP.NET Core MVC Web Application Project Structure” href=”https://devtutorial.io/asp-net-core-mvc-web-application-project-structure.html” target=”_blank”>ASP.NET Core MVC Web Application Project Structure

In the realm of CSS layout, lists are an interesting case. The items in a list are simply block boxes, but with an extra bit that doesn’t really participate in the document layout hanging off to one side. With an ordered list, that extra bit contains a series of increasing numbers (or letters) that are calculated and mostly formatted by the user agent, not the author. Taking a cue from the document structure, the user agent generates the numbers and their basic presentation.

None of this content generation could be described in CSS1 terms—and, therefore, it couldn’t be controlled—but CSS2 introduced features that allow list-item numbering to be described. As a result, CSS now lets you, the author, define your own counting patterns and formats, and associate those counters with any element, not just ordered list items. Furthermore, this basic mechanism makes it possible to insert other kinds of content, including text strings, attribute values, or even external resources into a document. Thus, it becomes possible to use CSS to insert link icons, editorial symbols, and more into a design without having to create extra markup.

To see how all these list options fit together, we’ll explore basic list styling before moving on to examine the generation of content and counters.


In a sense, almost anything that isn’t narrative text can be considered a list. The US Census, the solar system, my family tree, a restaurant menu, and even all of the friends you’ve ever had can be represented as a list, or perhaps as a list of lists. These many variations make lists fairly important, which is why it’s a shame that list styling in CSS isn’t more sophisticated.

The simplest (and best-supported) way to affect a list’s styles is to change its marker type. The marker of a list item is, for example, the bullet that appears next to each item in an unordered list. In an ordered list, the marker could be a letter, number, or a symbol from some other counting system. You can even replace the markers with images. All of these are accomplished using the different list-style properties.

Types of Lists

To change the type of marker used for a list’s items, use the property list-style-type.

That’s quite a few keywords, I know—and that’s not even all the values that list-style-type has historically borne! Some, such as urdu and hangul-consonant, are supported by one browser or another, but none of the older values have widespread support. By contrast, the list of values shown above has nearly universal support. Some examples are shown in Figure 15-1.

The list-style-type property, as well as all other list-related properties, can be applied only to an element that has a display of list-item, but CSS doesn’t distinguish between ordered and unordered list items. Thus, you can set an ordered list to use discs instead of numbers. In fact, the default value of list-style-type is disc, so you might theorize that without explicit declarations to the contrary, all lists (ordered or unordered) will use discs as the marker for each item. This would be logical, but, as it turns out, it’s up to the user agent to decide. Even if the user agent doesn’t have a predefined rule such as ol {list-style-type: decimal;}, it may prohibit ordered markers from being applied to unordered lists, and vice versa. You can’t count on this, so be careful.

Figure 15-1. A sampling of list style types

Historically, user agents treated any unrecognized keyword value as decimal, as per CSS 2.1. The CSS Lists and Counters Module is less precise about this, as of early 2017, and appears to allow a fallback to either disc or none. (Chrome, for example, defaults to none if an ordered list type is applied to an unordered list.)

If you want to suppress the display of markers altogether, then none is the value you should use. none causes the user agent to refrain from putting anything where the marker would ordinarily be, although it does not interrupt the counting in ordered lists. Thus, the following markup would have the result shown in Figure 15-2:

ol li {list-style-type: decimal;}
li.off {list-style-type: none;}
<li>Item the first
<li class="off">Item the second
<li>Item the third
<li >Item the fourth
<li>Item the fifth

Figure 15-2. Switching off list-item markers

list-style-type is inherited, so if you want to have different styles of markers in nested lists, you’ll likely need to define them individually. You may also have to explicitly declare styles for nested lists because the user agent’s style sheet may have already defined them. For example, assume that a user agent has the following styles defined:

ul {list-style-type: disc;}
ul ul {list-style-type: circle;}
ul ul ul {list-style-type: square;}

If this is the case—and it’s likely that this, or something like it, will be—you will have to declare your own styles to overcome the user agent’s styles. Inheritance won’t be enough.

String markers

CSS also allows authors to supply string values as list markers. This opens the field to anything you can input from the keyboard, as long as you don’t mind having the same string used for every marker in the list. Figure 15-3 shows the results of the following styles:

.list01 {list-style-type: "%";}
.list02 {list-style-type: "Hi! ";}
.list03 {list-style-type: "†";}
.list04 {list-style-type: "⌘";}
.list05 {list-style-type: " ";}

Figure 15-3. A sampling of string markers

As of late 2017, only the Firefox family of browsers supported string values for list markers.

List Item Images

Sometimes, a regular text marker just won’t do. You might prefer to use an image for each marker, which is possible with the property list-style-image.

Here’s how it works:

ul li {list-style-image: url(ohio.gif);}

Yes, it’s really that simple. One simple url() value, and you’re putting images in for markers, as you can see in Figure 15-4.

Figure 15-4. Using images as markers

Of course, you should exercise care in the images you use, as the example shown in Figure 15-5 makes painfully clear:

ul li {list-style-image: url(big-ohio.gif);}

Figure 15-5. Using really big images as markers

It’s generally a good idea to provide a fallback marker type in case your image doesn’t load, gets corrupted, or is in a format that some user agents can’t display. Do this by defining a backup list-style-type for the list:

ul li {list-style-image: url(ohio.png); list-style-type: square;}

The other thing you can do with list-style-image is set it to the default value of none. This is good practice because list-style-image is inherited, so any nested lists will pick up the image as the marker, unless you prevent that from happening:

ul {list-style-image: url(ohio.gif); list-style-type: square;}
ul ul {list-style-image: none;}

Since the nested list inherits the item type square but has been set to use no image for its markers, squares are used for the markers in the nested list, as shown in Figure 15-6.

Figure 15-6. Switching off image markers in sublists

Remember that this scenario might not occur in the real world: a user agent may have already defined a list-style-type for ul ul, so the value of square won’t be inherited after all. Instead, you might get a circle, disc, or other symbol.

Any image value is permitted for list-style-image, including gradient images. Thus, the following styles would have a result like that shown in Figure 15-7:

.list01 {list-style-image:
        orange, orange 60%, blue 60%, blue 95%, transparent);}
.list02 {list-style-image:
    linear-gradient(45deg, red, red 50%, orange 50%, orange);}
.list03 {list-style-image:
    repeating-linear-gradient(-45deg, red, red 1px, yellow 1px, yellow 3px);}
.list04 {list-style-image:
    radial-gradient(farthest-side at bottom right,
        lightblue, lightblue 50%, violet, indigo, blue, green,
        yellow, orange, red, lightblue);}

Figure 15-7. Gradient list markers

There is one drawback to gradient markers: they tend to be very small. The size isn’t something that CSS allows you to control, so you’re stuck with whatever the browser decides is a good size. This size can be influenced by things like font size, because the marker size tends to scale with the list item’s content, but that’s about it.


CSS does define a way to style list markers directly, the ::marker pseudo-element, but it wasn’t supported by anything as of early 2017.


As of early 2017, only the WebKit/Blink family of browsers supported gradient image values for list markers.

List-Marker Positions

There is one other thing you can do to influence the appearance of list items under CSS: decide whether the marker appears outside or inside the content of the list item. This is accomplished with list-style-position.

If a marker’s position is set to outside (the default), it will appear the way list items have since the beginning of the web. Should you desire a slightly different appearance, you can pull the marker in toward the content by setting the value of list-style-position to inside. This causes the marker to be placed “inside” the list item’s content. The exact way this happens is undefined, but Figure 15-8 shows one possibility:

li.first {list-style-position: inside;}
li.second {list-style-position: outside;}

Figure 15-8. Placing the markers inside and outside list items

In practice, markers given an inside placement are treated as if they’re an inline element inserted into the beginning of the list item’s content. This doesn’t mean the markers actually are inline elements—you can’t style them separately from the rest of the element’s content, unless you wrap all the other content in an element like span. It’s just that in layout terms, that’s what they act like.

List Styles in Shorthand

For brevity’s sake, you can combine the three list-style properties into a convenient single property: list-style.

For example:

li {list-style: url(ohio.gif) square inside;}

As you can see in Figure 15-9, all three values are applied to the list items.

Figure 15-9. Bringing it all together

The values for list-style can be listed in any order, and any of them can be omitted. As long as one is present, the rest will fill in their default values. For instance, the following two rules will have the same visual effect:

li.norm {list-style: url(img42.gif);}
li.odd {list-style: url(img42.gif) disc outside;} /* the same thing */

They will also override any previous rules in the same way. For example:

li {list-style-type: square;}
li {list-style: url(img42.gif);}

The result will be the same as that in Figure 15-9 because the implied list-style-type value of disc will override the previous declared value of square, just as the explicit value of disc overrides it in the second rule.

List Layout

Now that we’ve looked at the basics of styling list markers, let’s consider how lists are laid out in various browsers. We’ll start with a set of three list items devoid of any markers and not yet placed within a list, as shown in Figure 15-10.

Figure 15-10. Three list items

The border around the list items shows them to be, essentially, like block-level elements. Indeed, the value list-item is defined to generate a block box. Now let’s add markers, as illustrated in Figure 15-11.

Figure 15-11. Markers are added

The distance between the marker and the list item’s content is not defined by CSS, and CSS does as yet not provide a way to affect that distance.

With the markers outside the list items’ content, they don’t affect the layout of other elements, nor do they really even affect the layout of the list items themselves. They just hang a certain distance from the edge of the content, and wherever the content edge goes, the marker will follow. The behavior of the marker works much as though the marker were absolutely positioned in relation to the list-item content, something like position: absolute; left: -1.5em;. When the marker is inside, it acts like an inline element at the beginning of the content.

So far, we have yet to add an actual list container; in other words, there is neither a ul nor an ol element represented in the figures. We can add one to the mix, as shown in Figure 15-12 (it’s represented by a dashed border).

Figure 15-12. Adding a list border

Like the list items, the list element is a block box, one that encompasses its descendant elements. As we can see, however, the markers are not only placed outside the list item contents, but also outside the content area of the list element. The usual “indentation” you expect from lists has not yet been specified.

Most browsers, as of this writing, indent list items by setting either padding or margins for the containing list element. For example, the user agent might apply a rule such as:

ul, ol {margin-left: 40px;}

This is the basic rule employed by Internet Explorer and Opera. Most Gecko-based browsers, on the other hand, use a rule something like this:

ul, ol {padding-left: 40px;}

Neither is incorrect, but the discrepancy can lead to problems if you want to eliminate the indentation of the list items. Figure 15-13 shows the difference between the two approaches.

Figure 15-13. Margins and padding as indentation devices

The distance of 40px is a relic of early web browsers, which indented lists by a pixel amount. (Block quotes are indented by the same distance.) An alternate value might be something like 2.5em, which would scale the indentation along with changes in the text size.

For authors who want to change the indentation distance of lists, I strongly recommend that you specify both padding and margins to ensure cross-browser compatibility. For example, if you want to use padding to indent a list, use this rule:

ul {margin-left: 0; padding-left: 1em;}

If you prefer margins, write something like this instead:

ul {margin-left: 1em; padding-left: 0;}

In either case, remember that the markers will be placed relative to the contents of the list items, and may therefore “hang” outside the main text of a document, or even beyond the edge of the browser window. This is most easily observed if very large images, or long text strings, are used for the list markers, as shown in Figure 15-14.

Figure 15-14. Large markers and list layout

Generated Content

CSS defines methods to create what’s called generated content. This is content inserted via CSS, but not represented either by markup or content.

For example, list markers are generated content. There is nothing in the markup of a list item that directly represents the markers, and you, the author, do not have to write the markers into your document’s content. The browser simply generates the appropriate marker automatically. For unordered lists, the marker will a symbol of some kind, such as a circle, disc, or square. In ordered lists, the marker is by default a counter that increments by one for each successive list item. (Or, as we saw in previous sections, you may replace either kind with an image or symbol.)

To understand how you can affect list markers and customize the counting of ordered lists (or anything else!), you must first look at more basic generated content.

Inserting Generated Content

To insert generated content into the document, use the ::before and ::after pseudo-elements. These place generated content before or after the content of an element by way of the content property (described in the next section).

For example, you might want to precede every hyperlink with the text “(link)” to mark them for printing. This is accomplished with a rule like the following, which has the effect shown in Figure 15-15:

a[href]::before {content: "(link)";}

Figure 15-15. Generating text content

Note that there isn’t a space between the generated content and the element content. This is because the value of content in the previous example doesn’t include a space. You could modify the declaration as follows to make sure there’s a space between generated and actual content:


It’s a small difference but an important one.

In a similar manner, you might choose to insert a small icon at the end of links to PDF documents. The rule to accomplish this would look something like:

a.pdf-doc::after {content: url(pdf-doc-icon.gif);}

Suppose you want to further style such links by placing a border around them. This is done with a second rule:

a.pdf-doc {border: 1px solid gray;}

The result of these two rules is illustrated in Figure 15-16.

Figure 15-16. Generating icons

Notice how the link border extends around the generated content, just as the link underline extended under the “(link)” text in Figure 15-15. This happens because by default, generated content is placed inside the element box of the element (unless the generated content is a list marker).

You can float or position generated content outside its parent element’s box. CSS2 did not permit this, but advances in CSS and browser support removed this restriction. Similarly, the old restrictions about what kinds of display values could be given to generated content have been erased, so that you apply block formatting to the generated content of an inline box, and vice versa. For example, consider:

em::after {content: " (!) "; display: block;}

Even though em is an inline element, the generated content will generate a block box. Similarly, given the following code, the generated content is made block-level:

h1::before {content: "New Section"; display: block; color: gray;}

The result is illustrated in Figure 15-17.

Figure 15-17. Generating block-level content

One interesting aspect of generated content is that it inherits values from the element to which it’s been attached. Thus, given the following rules, the generated text will be green, the same as the content of the paragraphs:

p {color: green;}
p::before {content: "::: ";}

If you want the generated text to be purple instead, a simple declaration will suffice:

p::before {content: "::: "; color: purple;}

Such value inheritance happens only with inherited properties, of course. This is worth noting because it influences how certain effects must be approached. Consider:

h1 {border-top: 3px solid black; padding-top: 0.25em;}
  border-bottom: 1px dotted black; margin-bottom: 0.5em;}

Since the generated content is placed inside the element box of the h1, it will be placed under the top border of the element. It would also be placed within any padding, as shown in Figure 15-18.

Figure 15-18. Taking placement into account

The bottom margin of the generated content, which has been made block-level, pushes the actual content of the element downward by half an em. In every sense, the effect of the generated content in this example is to break up the h1 element into two pieces: the generated-content box and the actual content box. This happens because the generated content has display: block. If you were to change it to display: inline, the effect would be as shown in Figure 15-19:

h1::before {content: "New Section"; display: inline; color: gray;

Figure 15-19. Changing the generated content to be inline

Note how the borders are placed and how the top padding is still honored. So is the bottom margin on the generated content, but since the generated content is now inline and margins don’t affect line height, the margin has no visible effect.

With the basics of generating content established, let’s take a closer look at the way the actual generated content is specified.

Specifying Content

If you’re going to generate content, you need a way to describe the content to be generated. As you’ve already seen, this is handled with the content property, but there’s a great deal more to this property than you’ve seen thus far.

You’ve already seen string and URI values in action, and counters will be covered later in this chapter. Let’s talk about strings and URIs in a little more detail before we take a look at the attr( ) and quote values.

String values are presented literally, even if they contain what would otherwise be markup of some kind. Therefore, the following rule would be inserted verbatim into the document, as shown in Figure 15-20:

h2::before {content: "<em>&para;</em> "; color: gray;}

Figure 15-20. Strings are displayed verbatim

This means that if you want a newline (return) as part of your generated content, you can’t use <br>. Instead, you use the string \A, which is the CSS way of representing a newline (based on the Unicode line-feed character, which is hexadecimal position A). Conversely, if you have a long string value and need to break it up over multiple lines, you escape out the line feeds with the \ character. These are both demonstrated by the following rule and illustrated in Figure 15-21:

h2::before {content: "We insert this text before all H2 elements because \
it is a good idea to show how these things work. It may be a bit long \
but the point should be clearly made.  "; color: gray;}

Figure 15-21. Inserting and suppressing newlines

You can also use escapes to refer to hexadecimal Unicode values, such as \00AB.


As of this writing, support for inserting escaped content such as \A and \00AB is not very widespread, even among those browsers that support some generated content.

With URI values, you simply point to an external resource (an image, movie, sound clip, or anything else the user agent supports), which is then inserted into the document in the appropriate place. If the user agent can’t support the resource you point it to for any reason—say, you try to insert an SVG image into a browser that doesn’t understand SVG, or try to insert a movie into a document when it’s being printed—then the user agent is required to ignore the resource completely, and nothing will be inserted.

Inserting attribute values

There are situations where you might want to take the value of an element’s attribute and make it a part of the document display. To pick a simple example, you can place the value of every link’s href attribute immediately after the links, like this:

a[href]::after {content: attr(href);}

Again, this leads to the problem of the generated content running smack into the actual content. To solve this, add some string values to the declaration, with the result shown in Figure 15-22:


Figure 15-22. Inserting URLs

This can be useful for print style sheets, as an example. Any attribute value can be inserted as generated content: alt text, class or id values—anything. An author might choose to make the citation information explicit for a block quote, like this:

blockquote::after {content: "(" attr(cite) ")"; display: block;
  text-align: right; font-style: italic;}

For that matter, a more complicated rule might reveal the text- and link-color values for a legacy document:

body::before {content: "Text: " attr(text) " | Link: " attr(link)
  " | Visited: " attr(vlink) " | Active: " attr(alink);
  display: block; padding: 0.33em;
  border: 1px solid; text-align: center; color: red;}

Note that if an attribute doesn’t exist, an empty string is put in its place. This is what happens in Figure 15-23, in which the previous example is applied to a document whose body element has no alink attribute.

Figure 15-23. Missing attributes are skipped

The text “Active: ” (including the trailing space) is inserted into the document, as you can see, but there is nothing following it. This is convenient in situations where you want to insert the value of an attribute only when it exists.


CSS2.x defines the returned value of an attribute reference as an unparsed string. Therefore, if the value of an attribute contains markup or character entities, they will be displayed verbatim.

Generated quotes

A specialized form of generated content is the quotation mark, and CSS2.x provides a powerful way to manage both quotes and their nesting behavior. This is possible due to the pairing of content values like open-quote and the property quotes.

Upon studying the value syntax, we find that other than the keywords none and inherit, the only valid value is one or more pairs of strings. The first string of the pair defines the open-quote symbol, and the second defines the close-quote symbol. Therefore, of the following two declarations, only the first is valid:

quotes: '"' "'";  /* valid */
quotes: '"';  /* NOT VALID */

The first rule also illustrates one way to put string quotes around the strings themselves. The double quotation marks are surrounded by single quotation marks, and vice versa.

Let’s look at a simple example. Suppose you’re creating an XML format to store a list of favorite quotations. Here’s one entry in the list:

 <quote>I hate quotations.</quote>
 <quotee>Ralph Waldo Emerson</quotee>

To present the data in a useful way, you could employ the following rules, with the result shown in Figure 15-24:

quotation: display: block;}
quote {quotes: '“' '”';}
quote::before {content: open-quote;}
quote::after {content: close-quote;}
quotee::before {content: " (";}
quotee::after {content: ")";}

Figure 15-24. Inserting quotes and other content

The values open-quote and close-quote are used to insert whatever quoting symbols are appropriate (since different languages have different quotation marks). They use the value of quotes to determine how they should work. Thus, the quotation begins and ends with a double quotation mark.

With quotes, you can define quotation patterns to as many nesting levels as you like. In English, for example, a common practice is to start out with a double quotation mark, and a quotation nested inside the first one gets single quotation marks. This can be recreated with “curly” quotation marks using the following rules:

quotation: display: block;}
quote::before, q::before{content: open-quote;}
quote::after, q::after {content: close-quote;}

When applied to the following markup, these rules will have the effect shown in Figure 15-25:

 <quote> In the beginning, there was nothing. And God said: <q>Let there
  be light!</q> And there was still nothing, but you could see it.</quote>

Figure 15-25. Nested curly quotes

In a case where the nested level of quotation marks is greater than the number of defined pairs, the last pair is reused for the deeper levels. Thus, if we had applied the following rule to the markup shown in Figure 15-25, the inner quote would have had double quotation marks, the same as the outer quote:

quote {quotes: '\201C' '\201D';}

These particular rules used the hexadecimal Unicode positions for the “curly quote” symbols. If your CSS uses UTF-8 character encoding (and it really should), then you can skip the escaped hexadecimal position approach and just include the curly-quote characters directly, as in previous examples.

Generated quotes make possible one other common typographic effect. In situations where there are several paragraphs of quoted text, the close-quote of each paragraph is often omitted; only the opening quote marks are shown, with the exception of the last paragraph. This can be recreated using the no-close-quote value:

blockquote {quotes: '"' '"' "'" "'" '"' '"';}
blockquote p::before {content: open-quote;}
blockquote p::after {content: no-close-quote;}

This will start each paragraph with a double quotation mark but no closing mark. This is true of the last paragraph as well, so if you need to add a closing quote mark, you’d need to class the final paragraph and declare a close-quote for its ::after content.

This value is important because it decrements the quotation nesting level without actually generating a symbol. This is why each paragraph starts with a double quotation mark, instead of alternating between double and single marks until the third paragraph is reached. no-close-quote closes the quotation nesting at the end of each paragraph, and thus every paragraph starts at the same nesting level.

This is significant because, as the CSS2.1 specification notes, “Quoting depth is independent of the nesting of the source document or the formatting structure.” In other words, when you start a quotation level, it persists across elements until a close-quote is encountered, and the quote nesting level is decremented.

For the sake of completeness, there is a no-open-quote keyword, which has a symmetrical effect to no-close-quote. This keyword increments the quotation nesting level by one but does not generate a symbol.


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 two content values make it possible to define almost any counting format, including subsection counters employing multiple styles, such as “VII.2.c.”

Resetting and incrementing

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 property counter-reset.

A counter identifier is simply a label created by the author. For example, you might name your subsection counter subsection, subsec, ss, or bob. The simple act of resetting (or incrementing) an identifier is sufficient to call it into being. In the following rule, the counter chapter 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 that 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 is upper-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 a counter-reset declaration. The property in question is, not surprisingly, counter-increment.

Like counter-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 in counter-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 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.

Using counters

To actually display the counters, though, you need to use the content 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:

<list type="ordered">
 <item>First item</item>
 <item>Item two</item>
 <item>The third item</item>

By applying the following rules to XML employing this structure, you would get the result shown in Figure 15-26:

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 15-26. 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 with list-style-position: inside; declared.

Note also that the item elements are ordinary elements generating block-level boxes, which means that counters are not restricted only to elements with a display of list-item. In fact, any element can make use of a counter. Consider the following rules:

h1 {counter-reset: section subsec;
    counter-increment: chapter;}
h1::before {content: counter(chapter) ". ";}
h2 {counter-reset: subsec;
    counter-increment: section;}
h2::before {content: counter(chapter )"." counter(section) ". ";}
h3 {counter-increment: subsec;}
        counter(subsec) ". ";}

These rules would have the effect shown in Figure 15-27.

Figure 15-27 illustrates some important points about counter resetting and incrementing. For instance, notice how the counters are reset on the elements, whereas the actual generated-content counters are inserted via the ::before pseudo-elements. Attempting to reset counters in the pseudo-elements won’t work: you’ll get a lot of zeroes. You can increment them either on the elements or in the pseudo-elements, as you prefer.

Figure 15-27. Adding counters to headings

Also notice how the h1 element uses the counter chapter, 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 first h1 element in the document would be preceded by the text “1.0.0. ” because the counters section and subsec 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:

<code type="BASIC">
 <line>PRINT "Hello world!"</line>
 <line>REM This is what the kids are calling a "comment"</line>
 <line>GOTO 10</line>

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 the counter( ) format. You can do this by adding a comma-separated list-style-type keyword after the counter’s identifier. The following modification of the heading-counter example is illustrated in Figure 15-28:

h1 {counter-reset: section subsec;
    counter-increment: chapter;}
h1::before {content: counter(chapter,upper-alpha) ". ";}
h2 {counter-reset: subsec;
    counter-increment: section;}
h2::before {content: counter(chapter,upper-alpha)"." counter(section) ". ";}
h3 {counter-increment: subsec;}
        counter(subsec,lower-roman) ". ";}

Figure 15-28. Changing counter styles

Notice that the counter section was not given a style keyword, so it defaulted to the decimal counting style. You can even set counters to use the styles disc, circle, square, and none if you so desire.

One interesting point to note is that elements with a display of none do not increment counters, even if the rule seems to indicate otherwise. In contrast, elements with a visibility of hidden do increment counters:

.suppress {counter-increment: cntr; display: none;}
  /* 'cntr' is NOT incremented */
.invisible {counter-increment: cntr; visibility: hidden;}
  /* 'cntr' IS incremented */

Counters and scope

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 counter ordered is created at each level of nesting. So, for the first ordered list, an instance of ordered 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, suppose 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 with counter( ), but it can be done with counters( ). What a difference an “s” makes.

To create the nested-counter style shown in Figure 15-29, you need these rules:

ol {counter-reset: ordered; list-style: none;}
ol li:before {content: counters(ordered,".") ": "; counter-increment: ordered;}

Figure 15-29. Nested counters

Basically, the keyword counters(ordered,".") displays the ordered 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 the ordered 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 the content value causes a space, hyphen, and space to be added after all of those counters.

As with counter( ), 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 15-29 would all use lowercase letters for the counters instead of numbers:

ol li::before {counter-increment: ordered;
    content: counters(ordered,".",lower-alpha) ": ";}

You may have noticed that list-style: none was applied to the ol elements in the previous examples. That’s because the counters being inserted were generated content, not replacement list markers. In other words, had the list-style: none been left out, each list item would have had its user agent-supplied list counter, plus the generated-content counters we defined.

That ability can be very useful, but sometimes you really just want to redefine the markers themselves. That’s where counting patterns come in.

Defining Counting Patterns

In recent years, a new method of defining counter patterns has arisen in CSS. It uses the @counter-style block format, with a number of dedicated descriptors to manage the outcome. The general pattern is:

@counter-style <name> {

where <name> is an author-supplied name for the pattern in question. For example, to create a series of alternating triangle markers, the block might look something like this:

@counter-style triangles {
    system: cyclic;
    symbols: ▶ ▷;

This would have the result shown in Figure 15-30.

Figure 15-30. A simple counter pattern

As of early 2017, @counter-style and the related topics discussed in this section were only supported by the Firefox family of browsers. They’re fun to use, but don’t rely on them in copy—that is, don’t say things like “refer to step 1A” if your counters are being generated using @counter-style.

There are a number of descriptors available, summarized here.

We’ll start with simple systems and work our way up in complexity, but first, let’s see the precise definitions for the two most basic descriptors: system and symbols.

For pretty much any @counter-style block, those are the minimum two descriptors. You can leave out system if you’re defining a symbolic system, but it’s generally better to include it so that you’re clear about what kind of system you’re setting up. Remember, the next person to work on the styles may not be as familiar with counter styling as you!

Fixed Counting Patterns

The simplest kind of counter pattern is a fixed system. Fixed systems are used in cases where you want to define an exact sequence of counter markers that doesn’t repeat once you’ve run out of markers. Consider this example, which has the result shown in Figure 15-31:

@counter-style emoji {
    system: fixed;
    symbols:     ;
ul.emoji {list-style: emoji;}

Figure 15-31. A fixed counter pattern

Once the list gets past the fifth list item, the counter system runs out of emoji, and since no fallback was defined (we’ll get to that shortly), the markers for subsequent list items fall back to the default for unordered lists.

Notice that the symbols in the symbols descriptor are space-separated. If they were all jammed together with no space separation, as shown here, you’d get the result something like that seen in Figure 15-32:

@counter-style emoji {
    system: fixed;
    symbols:     ;

ul.emoji {list-style: emoji;}

Figure 15-32. When symbols get too close

This does mean you can define a fixed sequence of markers where each marker is composed of multiple symbols. (If you want to define a set of symbols that are combined in patterns to create a counting system, just wait: we’re getting to that soon.)

If you want to use ASCII symbols in your markers, it’s generally advisable to quote them. This avoids problems like angle brackets being mistaken for pieces of HTML by the parser. Thus you might do something like:

@counter-style emoji {
    system: fixed;
    symbols: # $ % ">";

It’s acceptable to quote all symbols, and it might be a good idea to get into the habit. That means more typing—the value above would become "#" "$" "%" ">"—but it’s less error-prone.

In fixed counter systems, you can define a starting value in the system descriptor itself. If you want to start the counting at 5, for example, you’d write:

@counter-style emoji {
    system: fixed 5;
    symbols:     ;

ul.emoji {list-style: emoji;}

In this case, the first five symbols represent counters 5 through 9. If the fallback counter style is decimal numbers, then the sixth counter in the sequence would have a value of 10 (in upper-Roman, it would be “J”).


This ability to set a starting number is not available in any of the other counter system types.

Cyclic Counting Patterns

The next step beyond fixed patterns is cyclic patterns. These are simply fixed patterns that repeat. Let’s take the fixed emoji pattern from the previous section and convert it to be cyclic. This will have the result shown in Figure 15-33:

@counter-style emojiverse {
    system: cyclic;
    symbols:     ;

ul.emoji {list-style: emojiverse;}

Figure 15-33. A cyclic counter pattern

The defined symbols are used in order, over and over, until there are no more markers left in the counting sequence.

It’s possible to use cyclic to supply a single marker that’s used for the entire pattern, much in the manner of supplying a string for list-style-type. In this case, it would look something like this:

@counter-style thinker {
    system: cyclic;
    symbols: ;
    /* equivalent to list-style-type: "" */

ul.hmmm {list-style: thinker;}

One thing you may have noticed is that so far, all our counters have been followed by a full stop (or a period, if you prefer). This is due to the default value of the suffix descriptor. suffix has a cousin descriptor, prefix.

With these descriptors, you can define symbols that are inserted before and after every marker in the pattern. Thus, we might give our thinker ASCII wings like so, as illustrated in Figure 15-34:

@counter-style wingthinker {
    system: cyclic;
    symbols: ;
    prefix: "~";
    suffix: "~";

ul.hmmm {list-style: wingthinker;}

Figure 15-34. Putting “wings” on the thinker

suffix is particularly useful if you want to remove the default suffix from your markers. Here’s one example of how to do so:

@counter-style thisisfine {
    system: cyclic;
    symbols:    ;
    suffix: "";

Of course, you could also extend the markers in creative ways using prefix and suffix, as shown in Figure 15-35:

@counter-style thisisfine {
    system: cyclic;
    symbols:   ;
    prefix: "";
    suffix: ;

Figure 15-35. This list is fine

You might wonder why the prefix value was quoted in that example, while the suffix value was not. There was literally no reason other than to demonstrate that both approaches work. As stated before, quoting symbols is safer, but it’s rarely required.

You may also see (or already been seeing) some differences between the Unicode glyphs in the CSS examples here, and those shown in the figures. This is an unavoidable aspect of using emoji and other such characters—what appears on one person’s user agent may be different on someone else’s. Just to pick one example: the differences in emoji rendering between Mac OS, iOS, Android, Samsung, Windows desktop, Windows mobile, Linux, and so on.

Keep in mind that you can use images for your counters, at least in theory. As an example, suppose you want to use a series of Klingon glyphs, which have no Unicode equivalents. (It’s a longstanding industry myth that Klingon is in Unicode. It was proposed in 1997 and rejected in 2001. A new proposal was made in 2016, with no resolution as of this writing.) We won’t represent the entire set of symbols here, but it would start something like this:

@counter-style klingon-letters {
    system: cyclic;
    symbols: url(i/klingon-a.svg) url(i/klingon-b.svg)
        url(i/klingon-ch.svg) url(i/klingon-d.svg)
        url(i/klingon-e.svg) url(i/klingon-gh.svg);
    suffix: url(i/klingon-full-stop.svg);

This would cycle from A through GH and then repeat, but still, you’d get some Klingon symbology, which might be enough. We’ll see ways to build up alphabetic and numeric systems later in the chapter.


As of early 2017, browser support for images as counting symbols was essentially nonexistent.

Symbolic Counting Patterns

A symbolic counting system is similar to a cyclic system, except in symbolic systems, the symbols add a repetition on each restart of the symbol sequence. This may be familiar to you from footnote symbols, or some varieties of alphabetic systems. Examples of each are shown here, with the result shown in Figure 15-36:

@counter-style footnotes {
    system: symbolic;
    symbols: "*" "†" "§";
    suffix: ' ';
@counter-style letters {
    system: symbolic;
    symbols: A B C D E;

Figure 15-36. Two patterns of symbolic counting

One thing to watch out for is that if you only have a few symbols applied to a very long list, the markers will quickly get quite large. Consider the letter counters shown in the previous example. Figure 15-37 shows what the 135th through 150th entries in a list using that system would look like.

Figure 15-37. Very long symbolic markers

This sort of consideration will become more of an issue from here on out, because the counter styles are all additive in one sense or another. There is a way to limit your exposure to these kinds of problems: the range descriptor.

With range, you can supply one or more space-separated pairs of values, with each pair separated from the others by commas. Let’s suppose we want to stop the letter-doubling after three iterations. There are five symbols, so we can restrict their use to the range of 1-15 like so, with the result shown in Figure 15-38 (which has been arranged in two columns to keep the figure size reasonable):

@counter-style letters {
    system: symbolic;
    symbols: A B C D E;
    range: 1 15;

Figure 15-38. Using range to limit a symbolic counter pattern

If there were, for whatever reason, a need to supply a second range of counter usage, it would look like this:

@counter-style letters {
    system: symbolic;
    symbols: A B C D E;
    range: 1 15, 101 115;

In that case, the symbolic letter system defined by letters would be applied in the range 1-15, and also 101-115 (which would be “AAAAAAAAAAAAAAAAAAAAA” through “EEEEEEEEEEEEEEEEEEEEEEE,” rather appropriately).

So what happens to the counters that fall outside of the range(s) defined by range? They fall back to a default marker style. You can leave that up to the user agent to handle, or you can provide some direction by means of the fallback descriptor.

As an example, you might decide to handle any beyond-the-range counters with Hebrew counting.

@counter-style letters {
    system: symbolic;
    symbols: A B C D E;
    range: 1 15, 101 115;
    fallback: hebrew;

You could just as easily use lower-greek, upper-latin, or even a non-counting style like square.

This will also be the style used as a fallback in any system where a counter can’t be represented by the primary system, for whatever reason. A simple example is a counting system that uses images for its symbols, and one of the images fails to load. In the following, assume south.svg fails to load. In that case, the missing image would be replaced with a lower-latin counter representing the value of the current item:

@counter-style compass {
    system: symbolic;
    symbols: url(north.svg) url(east.svg) url(south.svg) url(west.svg);
    fallback: lower-latin;

Alphabetic Counting Patterns

An alpahbetic counting system is very similar to a symbolic system, except the manner of repeating changes. Remember, with symbolic counting, the number of symbols goes up with each iteration through the cycle. In alphabetic systems, each symbol is treated as a digit in a numbering system. If you’ve spent any time in spreadsheets, this counting method may be familiar to you from the column labels.

To illustrate this, let’s reuse the letter symbols from the previous section, and change from a symbolic to an alphabetic system. The result is shown in Figure 15-39 (once again formatted as two columns for compactness’ sake):

@counter-style letters {
    system: alphabetic;
    symbols: A B C D E;
    /* once more cut off at 'E' to show the pattern’s effects more quickly */

Figure 15-39. Alphabetic counting

Notice how the second iteration of the pattern, which runs from “AA” to “AE” before switching over to “BA” through “BE”, then on to “CA” and so on. In the symbolic version of this, we’d already be up to “EEEEEE” by the time “EE” was reached in the alphabetic system.

Note that in order to be valid, an alphabetic system must have a minimum of two symbols supplied in the symbols descriptor. If only one symbol is supplied, the entire @counter-style block is rendered invalid. Any two symbols are valid; they can be letters, numbers, or really anything in Unicode, as well as images (again, in theory).

Numeric Counting Patterns

When you define a numeric system, you’re technically using the symbols you supply to define a positional numbering system. That is, the symbols are used as digits in a place-number counting system. Defining ordinary decimal counting, for example, would be done like this:

@counter-style decimal {
    system: numeric;

This is easily extensible to create hexadecimal counting, like so:

@counter-style hexadecimal {
    system: numeric;
    symbols: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 'A' 'B' 'C' 'D' 'E' 'F';

This will count from 1 through F, roll over to 10 and count up to 1F, then 20 to 2F, 30 to 3F, etc. Much more simply, it’s a breeze to set up binary counting:

@counter-style binary {
    system: numeric;
    symbols: '0' '1';

Examples of each of those three counting patterns are shown in Figure 15-40.

Figure 15-40. Three numeric counting patterns

An interesting question to consider is: what happens if a counter value is negative? In decimal counting, we generally expect negative numbers to be preceded by a minus sign (-), but what about in other systems, like symbolic? What if we define a letter-based numeric counting system? Or if we want to use accounting-style formatting, which puts negative values into parentheses? This is where the negative descriptor comes into play.

negative is like its own little self-contained combination of prefix and suffix, but is only applied in situations where the counter is a negative value. Its symbols are placed inside the prefix and suffix symbols.

So let’s say we want to use accounting-style formatting, and also add prefix and suffix symbols to all counters. That would be done as follows, with the result shown in Figure 15-41:

@counter-style accounting {
    system: numeric;
    negative: "(" ")";
    prefix: "$";
    suffix: " - ";
ol.kaching {list-style: accounting;}

Figure 15-41. Negative-value formatting

Another common feature of numeric counting systems is the desire to pad out low values so that their length matches that of higher values. For example, rather than “1” and “100,” a counting pattern might use leading zeroes to create “001” and “100.” This can be accomplished with the pad descriptor.

The pattern of this descriptor is a little interesting. The first part is an integer, and defines the number of digits that every counter should have. The second part is a string that’s used to fill out any value that has fewer than the defined number of digits. Consider this example:

@counter-style padded {
    system: numeric;
    suffix: '.';
    pad: 4 "0";

ol {list-style: decimal;}
ol.padded {list-style: padded;}

Given these styles, ordered lists will all used decimal counting by default: 1, 2, 3, 4, 5…. Those with a class of padded will use padded decimal counting: 0001, 0002, 0003, 0004, 0005…. An example of this is shown in Figure 15-42.

Figure 15-42. Padding values

Note how the padded counters use the 0 symbol to fill in any missing leading digits, in order to make every counter be at least four digits long. The “at least” part of that last sentence is important: if a counter gets up to five digits, it won’t be padded. More importantly, if a counter reaches five digits, none of the other shorter counters will get additional zeroes. They’ll stay four digits long, because of the 4 in 4 "0".

Any symbol can be used to pad values, not just 0. You could use underlines, periods, emoji, arrow symbols, empty spaces, or anything else you like. In fact, you can have multiple characters in the <symbol> part of the value. The following is perfectly acceptable, if not necessarily desirable:

@counter-style crazy {
    system: numeric;
    suffix: '.';
    pad: 4 "";

ol {list-style: decimal;}
ol.padded {list-style: padded;}

Given a counter value of 1, the result of that crazy counting system would be “1.”

Note that negative symbols count toward symbol length, and thus eat into padding. Also note that the negative sign will come outside any padding. Thus, given the following styles, we’d get the result shown in Figure 15-43:

@counter-style negativezeropad {
    system: numeric;
    suffix: '. ';
    negative: '–';
    pad: 4 "0";
@counter-style negativespacepad {
    system: numeric;
    suffix: '. ';
    negative: '–';
    pad: 4 " ";

Figure 15-43. Padding values

Additive Counting Patterns

We have one more system type to explore, which is additive-symbol counting. In additive counting systems, different symbols are used to represent values. Putting a number of the symbols together properly, and adding up the numbers each represents, yields the counter value.

It’s much easier to show this than explain it. Here’s an example adapted from Kseso:

@counter-style roman {
    system: additive;
        1000 M, 900 CM, 500 D, 400 CD,
        100 C, 90 XC, 50 L, 40 XL,
        10 X, 9 IX, 5 V, 4 IV, 1 I;

This will count in classical Roman style. Another good example can be found in the specification for counting styles, which defines a dice-counting system:

@counter-style dice {
    system: additive;
    additive-symbols: 6 ⚅, 5 ⚄, 4 ⚃, 3 ⚂, 2 ⚁, 1 ⚀, 0 "__";
    suffix: " ";

The results of both counting systems are shown in Figure 15-44; this time, each list has been formatted as three columns.

Figure 15-44. Additive values

Symbols can be quoted for clarity; e.g., 6 "⚅", 5 "⚄", 4 "⚃", and so on.

The most important thing to keep in mind is that the order of the symbols and their equivalent values matters. Notice how both the Roman and dice-counting systems supplied values from largest to smallest, not the other way around? That’s because if you put the values in any order other than descending, the entire block is rendered invalid.

Also notice the use of the additive-symbols descriptor instead of symbols. This is important to keep in mind, since defining an additive system and then trying to use the symbols descriptor will render the entire counter-styles block invalid. (Similarly, attempting to use the additive-symbols description in non-additive systems will render those blocks invalid.)

One last thing to note about additive systems is that, due to the way the additive-counter algorithm is defined, it’s possible to create additive systems where some values can’t be represented even though it seems like they should be. Consider this definition:

@counter-style problem {
    system: additive;
    additive-symbols: 3 "Y", 2 "X";
    fallback: decimal;

This would yield the following counters for the first five numbers: 1, X, Y, 4, YX. You might think “4” should be “XX,” and that may make intuitive sense, but the algorithm for additive symbols doesn’t permit it. To quote the specification: “While unfortunate, this is required to maintain the property that the algorithm runs in linear time relative to the size of the counter value.”


So how does Roman counting manage to get “III” for 3? Again, the answer is in the algorithm. It’s a little too complicated to get into here, so if you’re truly curious, I recommend you read the CSS Counter Styles Level 3 specification, which defines the additive counting algorithm. Even if that doesn’t interest you, just remember: make sure you have a symbol whose value equates to 1, and you’ll avoid this problem.

Extending Counting Patterns

There may come a time when you just want to vary an existing counting system a bit. For example, suppose you want to change regular decimal counting to use close-parentheses symbols as suffixes, and pad up to two leading zeroes. You could write it all out longhand, like so:

@counter-style mydecimals {
    system: numeric;
    suffix: ") ";
    pad: 2 "0";

That works, but it’s clumsy. Well, worry not: extends is here to help.

extends is sort of a system type, but only in the sense that it builds on an existing system type. The previous example would be rewritten with extends as follows:

@counter-style mydecimals {
    system: extends decimal;
    suffix: ") ";
    pad: 2 "0";

That takes the existing decimal system familiar from list-style-type and reformats it a bit. Thus, there’s no need to re-type the whole symbol chain. You just adjust the options, as it were.

In fact, you can only adjust the options: if you try use either symbols or additive-symbols in an extneds system, the entire @counter-style block will be invalid and ignored. In other words, symbols cannot be extended. As an example, you can’t define hexadecimal counting by extending decimal counting.

However, you can vary the hexadecimal counting for different contexts. As an example, you could set up basic hex counting and then define some variant display patterns, as shown in the following code and illustrated in Figure 15-45. (Note that each list jumps from 19 to 253, thanks to a value="253" on one of the list items.)

@counter-style hexadecimal {
    system: numeric;
@counter-style hexpad {
    system: extends hexadecimal;
    pad: 2 "0";
@counter-style hexcolon {
    system: extends hexadecimal;
    suffix: ": ";
@counter-style hexcolonlimited {
    system: extends hexcolon;
    range: 1 255; /* stops at FF */

Figure 15-45. Various hexadecimal counting patterns

Notice how the last of the four counter styles, hexcolonlimited, extends the third, hexcolon, which itself extends the first, hexadecimal. In hexcolonlimited, the hexadecimal counting stops at FF (255), thanks to the range: 1 255; declaration.

Speaking Counting Patterns

While it’s fun to build counters out of symbols, the result can be a real mess for spoken technologies such as Apple’s VoiceOver or the JAWS screen reader. Imagine, for example, a screen reader trying to read dice counters, or phases of the moon. To help, the speak-as descriptor allows you to define an audible fallback.

Let’s take the values backward. With a <counter-style-name>, you’re able to define an alternate counting style that the user agent likely already recognizes. For example, you might provide an audio fallback for dice-counting to be decimal counting when spoken:

@counter-style dice {
    system: additive;
    speak-as: decimal;
    suffix: " ";

Given those styles, the counter “⚅⚅⚂” would be spoken as “fifteen.” Alternatively, if the speak-as value is changed to lower-latin, that counter will be spoken as “oh” (capital letter O).

spell-out seems fairly straightforward, but it’s a little more complicated than it first appears. What is spelled out by the user agent is a “counter representation,” which is then spelled out letter by letter. It’s hard to predict what that will mean, since the method of generated a counter representation isn’t precisely defined: the specification says “counter representations are constructed by concatenating counter symbols together.” And that’s all.

words is similar to spell-out, except the counter representation is spoken as words instead of spelling out each letter. Again, the exact process is not defined.

With the value numbers, the counters are spoken as numbers in the document language. This is very similar to the previous code sample, where “⚅⚅⚂” is spoken as “fifteen,” at least in English documents. If it’s another language, then that language is used for counting: “quince” in Spanish, “fünfzehn” in German, “shíwǔ” in Chinese, and so on.

Given bullets, the user agent says whatever it says when reading a bullet (marker) in an unordered list. This may mean saying nothing at all, or producing an audio cue such as a chime or click.

Finally, consider the default value of auto. We saved this for last because its actual effect depends on the counting system in use. If it’s a alphabetic system, then speak-as: auto has the same effect as speak-as: spell-out. In cyclic systems, auto is the same as bullets. Otherwise, the effect is the same as speak-as: numbers.

The exception to this rule is if the system is an extends system, in which case auto’s effects are determined based on the system being extended. Therefore, given the following styles, the counters in an emojibrackets list will be spoken as if speak-as were set to bullets:

@counter-style emojilist {
    emojiverse {
    system: cyclic;
    symbols:     ;
@counter-style emojibrackets {
    system: extends emojilist;
    suffix: "]] ";
    speak-as: auto;


Even though list styling isn’t as sophisticated as we might like, and browser support for generated content is somewhat spotty (as of this writing, anyway), the ability to style lists is still highly useful. One relatively common use is to take a list of links, remove the markers and indentation, and thus create a navigation sidebar. The combination of simple markup and flexible layout is difficult to resist. With the anticipated enhancements to list styling in CSS3, I expect that lists will become more and more useful.

For now, in situations where a markup language doesn’t have intrinsic list elements, generated content can be an enormous help—say, for inserting content such as icons to point to certain types of links (PDF files, Word documents, or even just links to another web site). Generated content also makes it easy to print out link URLs, and its ability to insert and format quotation marks leads to true typographic joy. It’s safe to say that the usefulness of generated content is limited only by your imagination. Even better, thanks to counters, you can now associate ordering information to elements that are not typically lists, such as headings or code blocks. Now, if you want to support such features with design that mimics the appearance of the user’s operating system, read on. The next chapter will discuss ways to change the placement, shape, and even perspective of your design.

Comments are closed.