CSS content, counter-increment & counter-reset

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="http://www.yahoo.com">Yahoo</a></li>
  <li><a href="http://www.google.com">Google</a></li>
  <li><a href="http://evotech.net/blog">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;
This entry was posted in Web Development. Bookmark the permalink.

12 Responses to CSS content, counter-increment & counter-reset

  1. Jesse says:

    I’m not quite sure what you mean when you state, “The <ol> start attribute and <li> value element were deprecated in HTML 4.01 and not supported in XHTML strict.” <ol> is an element rather than an attribute, “start attribute” is nonsensical, and both <ol> and <li> elements are supported not only in HTML 4.01 and XHTML 1.0 strict but in the W3C’s XHTML 1.1 and XHTML Modularization 1.1 recommendations.

  2. Estelle Weyl says:

    I think you misunderstood me:

    “start” is, or rather, was, an attribute you could include in the <ol> element. Before you could write <ol start=”3″> But the “start” attribute is now deprecated. The “value” attribute of the <li> element is also deprecated. http://www.evotech.net/blog/2007/07/xhtml-deprecated-elements-and-attributes/ lists the deprecated elements and attributes.

  3. spark says:

    ohhh, this post is wonderful, I learned many here.

    but i really can’t point out where the problem of example “footnote” is. I have tried many other examples, they all can work correctly. but this one…

    I feel confusion..

  4. spark says:

    glade tell you, the problem i submit above is work out: the style your named “footnote”, but your included as class=”footenote”—there is a “e” after “foot”. it make me crazy.

  5. Estelle Weyl says:

    Thanks spark!

    I fixed the error. That was driving me crazy too.

  6. Pingback: CSS content, counter-increment 和 counter-reset详解| CSS| 前端观察

  7. oscar says:

    can I change content property using javascript?

  8. Anup says:

    Thanks for the useful citation example. I had to modify it slightly so that the citation number would continue incrementing across many more paragraphs (the example above meant it would revert to 1 for the first citation in a new paragraph).

    This is the CSS I used, though it doesn’t have to be this exactly:

    body { counter-reset: citations; }
    cite.footnote {counter-increment: citations; }
    cite.footnote:after {
    content: counter(citations);

    Hope that helps?

  9. BACH Richard says:

    Thanks a lot, this is a most useful site, I learned a lot, thank you very much.
    Best regards from a french visitor.

  10. Sylvain says:

    Thanks for giving us your time. I found your paper very interested and well documented.

  11. Chris says:

    great tutorial. Thanks for sharing.

  12. Troy III Ajnej says:

    Hi there – (good article).

    I always thought that this* CSS addition is freakily wrong!
    (a sort of “trespassing”)

    div:after {
    content: "crazy css writes content after every div";}

    CSS has crossed the line and is way deep into HTML private field of responsibility and ‘property’.
    CSS should not be allowed to: render; serve; or by any other means, generate contents not contained in HTML.

    CSS working team must be ‘warned’ about this ‘violation’ and should be once again reminded of its own definition of that:
    its sole purpose, responsibility and duty – is to generate presentational STYLE of the web-CONTENT and make not a single step outside of its own field of responsibility. Because we all know that: semantically correct content-delivery mechanism is HTML fundamental responsibility by definition and the very reason it first came to existence.

    CSS kids of W3C joint were also arguing about elements such as and similar to be obsolete, unnecessary, lacking semantic meaning and for being simply presentational – but that’s something to be expected from the mouth of an illiterate. Since a bold phrase in the middle of some plain meaningful content is purely semantic, and also a structural part of that content.

    We all know and they should know that by order: 1.Content; 2.Content Semantics; and 3.Content Structure, belong solely to HTML.

    On the other hand XHTML (saw it in the title) has nothing to do with any of these…
    – XHML is simply not HTML!
    Its main concern was structure (bones), skeleton delivery and never gave a damn about content delivery. Those who grew up eating bones, love xhtml -but clients want content to feed their intellectual needs. But to xml this concern was of a third hand importance. Or to be more precise, of no importance at all.

    Their primary concern was data-structure [omiting the data part] structure; that is, ‘bones’: -are they properly shaped? If not: The ‘flesh’ is no good – Return it!
    We don’t need empty present boxes. We need Information. So why don’t you change that xhtml part of title into something userful, that is: HTML?


Leave a Reply

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