Creating counters with Generated Content

I have never used the counter or increment properties since they aren’t supported in IE7 or earlier, nor are the :before pseudo elements, or content property. Since IE8 does support all of these, soon we may be able to include these CSS properties, so I thought I would explain them.

The <ol> start attribute and <li> value element were deprecated in HTML 4.01 and not supported in XHTML strict. The value attribute set the number of a list item, enabling the following list items to increment from that value. While there is no (X)HTML replacement for these elements, CSS 2.1 provides methods for setting and incrementing counters on any element, not just <li>’s. This article focuses on the following CSS pseudo elements and properties:

  • content CSS property
  • :before pseudo-element
  • :after pseudo-element
  • counter-increment CSS property
  • counter-reset CSS property

The content property is used in conjunction with the :before or :after pseudo-elements. The value of the content property is added to the visual layout of your document, but is NOT added to the DOM. If you’re reading this tutorial, you should already know that! We’re discussing :before, :after and content here because without them, the counter is kind of useless: if you’re not going to display the content of a counter before (or perhaps after) an element, why include it?

Overview of content property

To make understanding this tutorial easier, we are going to use the concrete example of adding ” – <hrefValue>” after every link, which is helpful when using print CSS.

<ul id="showlinkafterlink">
  <li><a href="">Yahoo</a></li>
  <li><a href="">Google</a></li>
  <li><a href="">CSS, JavaScript and XHTML explained</a></li>

Listing 1: Without any CSS, this is how the code above looks

#showlinkafterlink a:after {
    content: "  - <" attr(href) ">";

Listing 2: If you are using a standards compliant browser (i.e. NOT IE6 or IE7), the above should have the hrefs following the links.

A few things to note about the content rendered via the content property:

  • Generated content does not alter the document tree. The content is rendered only: it doesn’t appear in the DOM tree, altering the presentation only, not the document
  • To control the appearance of the generated content, you can use other CSS properties. All properties in the :after declaration impact the generated content.
  • In case you were wondering, you can only add one pseudo-element per side of your element. element:before:before does not work.
#showlinkafterlink a:after {
    content: "  - <" attr(href) ">";
    color: #ff0000;	 font-style: italic;

Listing 3: Here we’ve defined the color and font-style for the generated content.

While the generated content is NOT added to the DOM, think of it as an added span that inherits everything from it’s parent. The content cannot contain any HTML, just ASCII, escaped and ISO characters. As mentioned, content is used with the :before and :after pseudo-elements to generate content in a document.

Values of the CSS content property

The CSS content property can take as its value none | normal | <string> | url | open-quote | close-quote | no-open-quote | no-close-quote | attr(attribute) | counter(name[, style]). Values have the following meanings:

  • content: none;

    none: The pseudo-element is not generated.

  • content: normal;

    normal: Computes to ‘none’ for the :before and :after pseudo-elements unless the default browser rendering includes content (i.e. <q>) which is handled differently based on the browser – Safari shows the default content, IE8 and FF do not.

  • content: "Estelle: ";content: "\00a3 "; /* includes '£' */

    string: Adds a string before or after the element (depending on which pseudo-element is used). Strings can either be written with double or single quotes. If your string includes a quotation mark, make sure you escape it with a slash-quote or slash-ISO value. If you are going to include any non-ASCII characters or entities, declare the charset in your CSS and XHTML, and/or use the ISO code for the character. Check out my blog post on including named entities in CSS and Javascript

  • content: url(myBullet.gif);

    url: The value is a URI that designates an external resource (such as an image). If the browser can’t display the resource, FF and IE8 omit it, as if it were not specified, but Safari indicates that the resource cannot be displayed with a missing image icon.

  • content: open-quote;

    open-quote and close-quote: These values are replaced by the appropriate string from the ‘quotes’ property. Opera handles this, but does not nest quotes correctly, Safari ignores this completely. IE8 and Firefox get it right.

  • content: open-quote;

    no-open-quote and no-close-quote: Introduces no content, but increments (decrements) the level of nesting for quotes. Safari ignores this completely. Opera, IE8 and Firefox get it right.

  • content: attr(title);

    attr(x): This function returns as a string the value of attribute X for the subject of the selector. The string is not parsed by the CSS processor. If the subject of the selector doesn’t have an attribute X, an empty string is returned. The case-sensitivity of attribute names depends on the document language.

    For uber coolness, or geekiness as the case may be, you can add text dynamically without using javascript.

    a.tooltip {
      position: relative;
    a.tooltip:hover:after {
      content: attr(title);
      padding: 5px;
      border: 1px solid #f00;
      background-color: #dedede;
  • content: counter(subtitles, decimal);content: counter(headers) "." counter(subtitles) ": ";

    or counter(name, style): The counter takes two parameters: the name, which you can reference to increment or reset, and the style, which, if not declared, defaults to “decimal”. While you can name the counter almost anything except ‘none’, ‘inherit’ or ‘initial’, avoid key terms.

Browser support for the CSS content property and :before and :after pseudo elements

The CSS content property and possible values
IE6 IE7 IE7 in IE8 IE8 FF3 FF 3.5 Beta Saf 3.2 Saf 4 Beta Opera 9.64
content Since :before and :after is not supported in these browsers, testing is not possible, and moot. It is assumed that IE6 and IE7 does not support the content property, therefore supports none of these values works, except for issues below works, except for issues below works, except for issues below
none n
normal displays quotes on <q> Makes sense, but not the spec.
url() nothing nothing nothing missing image icon missing image icon missing image alt
Does not nest quotes correctly, but does include quotes.

counter-increment and counter-reset CSS properties

Counters don’t work on their own! if you just write p:before {content: counter(subtitles, decimal);} every paragraph will have a zero in front of it. To more easily understand this, let’s think of real world examples:

  • footnotes
  • creating numbering for outlines: counting chapters, sections and subsections, restarting the subsection counter for each new section, and resetting the section counter for each new chapter

Using the CSS counter syntax you can define as many counters as you like in your page, increment the counters and reset the counters. While the counter gets physically added to the presentation of the page (not the DOM) using the CSS counter value on the content property as a pseudo element using the :before or :after syntax, the increment happens on an actual element on the page.

<p> With this paragraph, I have included <cite class="footnote">citation to footnote</cite>.</p>

cite.footnote {counter-increment: citations;}

cite.footnote:after {content: counter(citations); vertical-align:text-top;}

In our example above, we would increment the counter on every <cite class="footnote">, then add the footnote numbers using the content property on the :after pseudo element. In order to use a counter, you should give it a name. In the above scenario, the name is “citations”. You can also specify the style. If the style is not declared, the style defaults to decimal. The values include all the list-style-type values, though only <ol> values make sense with a counter. Values include decimal | decimal-leading-zero | lower-alpha | lower-greek | lower-roman | upper-alpha | upper-roman | lower-latin | upper-latin | hebrew | georgian and others.

You can include more than one counter in generated content. For example, in legalese, you often have sections within sections all numbered. This is doable with CSS counters.

<h1>First Header</h1>
<h2>another subsection</h2>
<h2>yet another subsection</h2>
<p>more text....</p>
<h1>Another Header</h1>
<h2>another subsection</h2>
<h2>yet another subsection</h2>
<p>more text....</p>

To add counters in front of every h1, with counters on the h2s that reset after each h1, the CSS would look like:

h1 {
	counter-increment: headers;
	counter-reset: subsections;
h1:before {
	content: counter(headers, upper-roman);
h2 {
h2:before {
	content: counter(headers, upper-roman) "." counter(subsections, lower-roman) ":";

Now all <h2>s are preceded by their header number and subsection number.

A few things to note about the code: note that in the h2:before declaration we’ve included two counters: the header counter and the subsection counter. We declared the style in both calls, as style is NOT inherited. Also, we’ve included strings within our declaration. Note that there are quotes delimiting our strings, but not our counters, and there are no concatenation elements without our content value. To combine multiple counter ID’s in the same style attribute, string them together using space delimited counter ID values.

Incrementing of the counter was done through the counter-increment declaration. While the default value is to increment by 1, we can increment by other values. You can also reset counters. It makes sense to reset subsections after every header. To overwrite the default value of 1, and to reset after each <h1> the CSS could be:

h1 {
  counter-increment: headers 10;
  counter-reset: subsections 5;
h2 {
  counter-increment:subsections 2;

2 thoughts on “Creating counters with Generated Content”

Leave a Reply

Your email address will not be published. Required fields are marked *