Overview And ExamplesHow To Benefit From CSS Generated Content And Counters

Advertisement

Generated content was first introduced in the CSS2 specification. For several years, the feature was used by relatively few Web authors due to inconsistent browser support. With the release of Internet Explorer 8 in 2009, generated content was rediscovered, and many interesting implementations were adopted for the first time. In this article, we’ll discuss some possible uses of generated content1.

What Is Generated Content?

In technical terms, generated content is a simple abstraction created by CSS in the document tree. As such, in practical terms, generated content exists only in the layout of the Web document.

Accessing generated content via JavaScript is possible by reading the textual value of the content property:


var test = document.querySelector('#test');
var result   = getComputedStyle(test, ':before').content;
var output = document.querySelector('#output');
output.innerHTML = result;

Inserting Generated Content

Generated content may be inserted before and after the actual content of an element, using the :before and :after pseudo-elements, respectively. To represent the pseudo-elements, we can use the following pseudo-markup.


<p>
   <before>Start</before>
      Actual content
   <after>End</after>
</p>

And our CSS would be:


p:before {
   content: "Start";
}

p:after {
   content: "End";
}

Bear in mind that if you are validating the CSS file against the CSS3 specifications, the :before and :after pseudo-elements should be written as ::before and ::after. Otherwise, the CSS validator will call an error.

As you can see, the property that inserts the two strings is content. This
property accepts the following values:

  • none, normal
    The pseudo-content would not be generated.
  • <string>
    This would be a textual string enclosed in quotation marks.
  • url()
    This function enables us to insert an external resource (usually an image), as with the background-image property.
  • counter(), counters()
    These functions insert counters (see below for details).
  • attr(attribute)
    This function enables us to insert the value of attribute of a given element.
  • open-quote, close-quote, no-open-quote, no-close-quote
    These values automate the generation of quotation marks.

Keep in mind that generated content takes up space on the page, and its presence affects the browser’s computation of the parent element.

Inserting Strings

In the previous example, we inserted two simple strings before and after the actual content of the element. Generated content also enables us to insert more complex symbols, through escaping:


p:before {
   content: "0A7";
   padding-right: 0.2em;
}

The escaped sequence between the double quotation marks is the hexadecimal Unicode value of the paragraph symbol. We can also combine simple strings with Unicode symbols:


p:before {
   content: "( " "0A7" " )";
   padding-right: 0.2em;
}

In case you need it, a comprehensive list of all Unicode characters is available on Alan Wood’s website6.

Note that all textual content inside the content property is treated literally. So, spaces and tabs inserted via the keyboard will be inserted on the page as well.

Inserting Icons Using Web Fonts

Web fonts can be used to insert graphical icons through generated content. Depending on the Web font family, you can insert either simple letters or Unicode sequences:


@import url(http://weloveiconfonts.com/api/?family=brandico);

p:before {
   content: "f303";
   padding-right: 0.3em;
   font-family: 'brandico', sans-serif;
   font-size: 22px;
}

In this example, we have inserted a Twitter icon. Our code could be rewritten as follows:


.icon-twitter:before {
   content: "f303";
   padding-right: 0.3em;
   font-family: 'brandico', sans-serif;
   font-size: 22px;
}

Inserting Images

We can insert images through the url() function.


a:before {
   content: url(link.png);
   padding-right: 0.2em;
}

As you can see, this function has the same syntax as the background-image property.

Inserting Attribute Values

An attribute value of an element can be inserted through the attr() function.


a[href]:after {
   content: "( " attr(href) " )";
   padding-left: 0.2em;
   color: #000;
   font: small "Courier New", Courier, monospace;
}

We’ve just inserted the value of the href attribute, which is a simple text string.

Inserting Counters

The automatic numbering of CSS is controlled by two properties,
counter-reset and counter-increment. Counters defined by these properties are then used with the counter() and counters() functions of the content property.

The counter-reset property may contain one or more names of counters (i.e. “identifiers”), optionally followed by an integer. The integer sets the value that will be incremented by the counter-increment property for any occurence of the given element. The default value is 0. Negative values are allowed.

The counter-increment property is similar. The basic difference is that this one increments a counter. Its default increment is 1. Negative values are allowed.

Now we are ready for an example. Take the following markup:


<dl>
   <dt>term</dt>
   <dd>description</dd>
   <dt>term</dt>
   <dd>description</dd>
   <dt>term</dt>
   <dd>description</dd>
</dl>

We want to add progressive numbering (1, 2, 3, etc.) to each definition term (dt) in the list. Here is the CSS:


dl {
   counter-reset: term;
}
dt:before {
   counter-increment: term;
   content: counter(term);
}

The first rule here sets a counter for the definition list. This is called a “scope.” The name (or identifier) of the counter is term. Whatever name we choose for our counter must be identical to the one in the counter-increment property (of course, the name should be meaningful).

In the second rule, we attach the :before pseudo-element to the dt element, because we want to insert the counter precisely before the actual content of the element. Let’s take a closer look at the second declaration of the second rule. The counter() function accepts our identifier (term) as its argument, and the content property generates the counter.

There’s no space between the number and the content of the element. If we want to add a space and, say, a period after the number, we could insert the following string in the content property:


dt:before {
   content: counter(term) ". ";
}

Note that the string between the quotation marks is treated literally; that is, the space after the period is inserted just as we’ve typed it on the keyboard. In fact, the content property can be regarded as the CSS counterpart of the JavaScript document.write() method, except that it doesn’t add real content to the document. Simply put, the content property creates a mere abstraction in the document tree but doesn’t modify it.

In case you’re wondering, we can add more styles to counters by applying other properties to the attached pseudo-element. For example:


dt:before {
   content: counter(term);
   padding: 1px 2px;
   margin-right: 0.2em;
   background: #ffc;
   color: #000;
   border: 1px solid #999;
   font-weight: bold;
}

We’ve just set a background color, added some padding and a right margin, made the font bold, and outlined the counters with a thin solid border. Now our counters are a little more attractive.

Furthermore, counters may be negative. When dealing with negative counters, we should adhere to a little math — namely, the part about adding and subtracting negative and positive numbers. For example, if we need progressive numbering starting from 0, we could write the following:


dl {
   counter-reset: term -1;
}
dt:before {
   counter-increment: term;
   content: counter(term) ". ";
}

By setting the counter-reset property to -1 and incrementing it by 1, the resulting value is 0, and the numbering will start from that value. Negative counters may be combined with positive counters to interesting effect. Consider this example:


dl {
   counter-reset: term -1;
}
dt:before {
   counter-increment: term 3;
   content: counter(term) ". ";
}

As you can see, adding and subtracting negative and positive numbers yield a wide range of combinations between counters. With just a simple set of calculations, we get complete control over automatic numbering.

Another interesting feature of CSS counters lies in their ability to be nested. In fact, numbering may also be ordered by progressive sublevels, such as 1.1, 1.1.1, 2.1 and so on. To add a sublevel to the elements in our list, we would write the following:


dl {
   counter-reset: term definition;
}
dt:before {
   counter-increment: term;
   content: counter(term) ". ";
}
dd:before {
   counter-increment: definition;
   content: counter(term) "." counter(definition) " ";
}

This example is similar to the first one, but in this case we have two counters, term and definition. The scope of both counters is set by the first rule and “lives” in the dl element. The second rule inserts the first counter before each definition term in the list. This rule is not particularly interesting because its effect is already known. Instead, the last rule is the heart of our code because it does the following:

  1. increments the second counter (definition) on dd elements;
  2. inserts the first counter (term), followed by a period;
  3. inserts the second counter (definition), followed by a space.

Note that steps 2 and 3 are both performed by the content property used on the :before pseudo-element that is attached to the definition term.

Another interesting point is that counters are “self-nesting,” in the sense that resetting a counter on a descendant element (or pseudo-element) automatically creates a new instance of the counter. This is useful in the case of (X)HTML lists, where elements may be nested with arbitrary depth. However, specifying a different counter for each list is not always possible because it might produce rather redundant code. For this reason, the counters() function is useful. This function creates a string that contains all of the counters with the same name of the given counter in the scope. Counters are then separated by a string. Take the following markup:


<ol>
   <li>item</li>
   <li>item
      <ol>
         <li>item</li>
         <li>item</li>
         <li>item
            <ol>
               <li>item</li>
               <li>item</li>
            </ol>
         </li>
      </ol>
   </li>
</ol>

The following CSS will number the nested list items as 1, 1.1, 1.1.1, etc.


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

In this example, we have only the item counter for each nested level. Instead of writing three different counters (such as item1, item2, item3) and thus creating three different scopes for each nested ol element, we can rely on the counters() function to achieve this goal. The second rule is important and deserves further explanation. Because ordered lists have default markers (i.e. numbers), we’d get rid of these markers by turning the list items into block-level elements. Remember that only elements with display: list-items have markers.

Now we can look carefully at the third rule, which does the actual the work. The first declaration increments the counter previously set on the outermost list. Then, in the second declaration, the counters() function creates all of the counter’s instances for the innermost lists. The structure of this function is as follows:

  1. Its first argument is the name of the given counter, immediately followed by a comma.
  2. Its second argument is a period between double quotation marks.

Note that we’ve inserted a space after the counters() function to keep the numbers separate from the actual contents of the list items.

Counters are formatted with decimal numbers by default. However, the styles of the list-style-type property are also available for counters. The default notation is counter(name) (i.e. no styling) or counter(name, 'list-style-type') to change the default formatting. In practice, the recommended styles are these:

  • decimal
  • decimal-leading-zero
  • lower-roman
  • upper-roman
  • lower-greek
  • lower-latin
  • upper-latin
  • lower-alpha
  • upper-alpha

Don’t forget that we’re working with numeric systems. Also remember that the specification doesn’t define how to render an alphabetical system beyond the end of an alphabet. For example, the rendering of lower-latin after 26 list items is undefined. Thus, numerals are recommended for long lists:


dl {
   counter-reset: term definition;
}
dt:before {
   counter-increment: term;
   content: counter(term, upper-latin) ". ";
}
dd:before {
   counter-increment: definition;
   content: counter(definition, lower-latin) ". ";
}

We can also add styles to the counters() function:


li:before {
   counter-increment: item;
   content: counters(item, ".", lower-roman) " ";
}

Note that the counters() function also accepts a third argument (lower-roman) as the last item in its arguments list, separated from the preceding period by a second comma. However, the counters() function doesn’t allow us to specify different styles for each level of nesting.

Conclusion

With the new generation of browsers, we can use CSS-generated content to embellish our layouts with strings and graphics. Generated content, then, is surely an excellent tool that every developer should learn.

Further Reading

Source of image on front page: Riebart21

(al)

Footnotes

  1. 1 http://www.w3.org/TR/CSS21/generate.html
  2. 2 http://jsfiddle.net/gabrieleromanato/QSNeJ/
  3. 3 http://jsfiddle.net/gabrieleromanato/d9863/
  4. 4 http://jsfiddle.net/gabrieleromanato/69XSC/
  5. 5 http://jsfiddle.net/gabrieleromanato/umgSs/
  6. 6 http://www.alanwood.net/unicode/
  7. 7 http://jsfiddle.net/gabrieleromanato/F786N/
  8. 8 http://jsfiddle.net/gabrieleromanato/WSwWJ/
  9. 9 http://jsfiddle.net/gabrieleromanato/jjuae/
  10. 10 http://jsfiddle.net/gabrieleromanato/hBmVA/
  11. 11 http://jsfiddle.net/gabrieleromanato/CWTTZ/
  12. 12 http://jsfiddle.net/gabrieleromanato/tJU7A/
  13. 13 http://jsfiddle.net/gabrieleromanato/GJPZq/
  14. 14 http://jsfiddle.net/gabrieleromanato/D6bBU/
  15. 15 http://jsfiddle.net/gabrieleromanato/Ampkg/
  16. 16 http://jsfiddle.net/gabrieleromanato/7mcWp/
  17. 17 http://jsfiddle.net/gabrieleromanato/W6HYP/
  18. 18 http://jsfiddle.net/gabrieleromanato/wLtAf/
  19. 19 http://www.smashingmagazine.com/2011/07/13/learning-to-use-the-before-and-after-pseudo-elements-in-css/
  20. 20 http://www.smashingmagazine.com/2011/03/19/styling-elements-with-glyphs-sprites-and-pseudo-elements/
  21. 21 http://www.flickr.com/photos/riebart/4466482623

↑ Back to topShare on Twitter

Gabriele Romanato is a web developer. Contributor of the W3C CSS Test Suite, he is also skilled with jQuery and WordPress. You can find more about him by visiting his blog or his Facebook page. He is also on Twitter.

Advertising
  1. 1

    Wow, great stuff here, Gabriele. Always nice to see a detailed look at something specific in CSS.

    One thing I should point out is that the content property has a new value that was added in CSS3, that is part of the spec. However, as you can see at that link I just included, that value is “at risk”. It seems maybe no browser is supporting it, so, as is always the case, lack of implementation will eventually lead to it being removed from the spec.

    0
    • 2

      Gabriele Romanato

      April 12, 2013 9:15 am

      Thanks. I knew this new feature but I forgot to mention it probably because I still feel like an old-school CSS fan. :-)

      0
  2. 3

    [quote]Accessing generated content via JavaScript is possible by reading the textual value of the content property…[/quote]

    I can’t believe I just read that! I spent days looking for how to do this on the web the other week. I need to play with this right now.

    Good read – thanks Gabe!

    0
  3. 5

    You can use :before even with CSS3 (it won’t trigger any error), as “for compatibility with existing style sheets, user agents must also accept the previous one-colon notation for pseudo-elements introduced in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and :after).” ;-)
    http://www.w3.org/TR/css3-selectors/#pseudo-elements

    0
    • 6

      Gabriele Romanato

      April 12, 2013 11:12 am

      Right. Thanks. :-)

      0
    • 7

      Double-colon is more correct here, for sure. The article discusses pseudo-elements (double-colon) and not pseudo-classes (single-colon).

      In the interests of putting more correct code out in the wild, it is worth changing all of these examples to double-colon? The code will be copy-pasted, after all. Is there a reason why the examples are all single-colon?

      0
      • 8

        Depends on your support requirements. Although single colon technically doesn’t validate, IE8 doesn’t support double colon syntax AFAIK. I’d rather it worked in all browsers than worry about validation, personally.

        0
      • 9

        The spec’ – http://www.w3.org/TR/css3-selectors/#pseudo-elements – says single-colon syntax is valid for CSS < 3 pseudo-elements (yeah I <3 CSS) for compatibility's sake. So there is no interest to use double-colon syntax for these.

        0
  4. 10

    Great article, thanks for your hard work.

    0
  5. 11

    Yeah, I like CSS content very much :-)

    Just a side note on the example with the numbered definition list:
    I like it better when the counter on the definition items always starts by one (1) again.

    So instead of:
    1. term1
    1.1 def1.1
    1.2 def1.2
    2. term2
    2.3 def2.1
    2.4 def2.2

    I’ll have:
    1. term1
    1.1 def1.1
    1.2 def1.2
    2. term2
    2.1 def2.1
    2.2 def2.2

    You can achieve this effect by separating the counter-reset statement, like this:
    dl { counter-reset: term; }
    dt { counter-reset: definition; }

    Thanks for the article anyway!

    0
  6. 13

    I was wondering what was the ‘content’ attr about, thanks for this article.
    I have a cuestion:
    To make it css3 valid and backward compatible, should i use both versions of the pseudo selector (ex elm:after and elm::after), does the order matter?

    0
  7. 14

    Thanks Gabriele, for a detailed article. Now, I am planning on how this can be used in so many ways.

    0
  8. 15

    css genereted content is very useful to upgrade any site on internet to gain business profits thanks for so detail information that I can use it for my site http://internetmarketing-media.com/ also

    0
  9. 16

    Generating content with CSS is great, but it is also a great failure for accessibility of this content.
    To respect WCAG 2.0, (please see http://www.w3.org/TR/WCAG20-TECHS/F87.html) you must not use this technique to insert informative content. (unless an alternative for this content is provided somewhere else)

    So it is great only for decorative element.

    Faithfully.

    0
  10. 18

    That is great, and I think these attributes are supported by IE8 as well (who cares about IE7!) but what I wonder, if there was a way to display other than the set values for counters, like x, y, z… or 26 27 28 (as characters) which allows for letter-numbering in other than Latin letters.

    I know I know, I have to do my own digging

    0
  11. 19

    Your article is illustrated with some C sourcecode.

    Great stuff, though.

    0
  12. 20

    Thanks for detailed article. i think this technique is great for numbering comments.

    0
  13. 22

    Interesting article – great for decorative stuff, but as Eric suggested, not so great for accessibility.
    The attr(href) is brilliant for displaying URL’s in a print-based document (‘cos you can’t click the paper for the link!)
    Cheers

    0
  14. 23

    Thanks Gabriele Romanato , relay good stuff…..

    0
  15. 24

    Psuedo elements are elements to the rendering system, so use them as sparingly as you would any other element. JS is better than the psuedo element trick to create toolips, for example. Well, if you have a lot of them.

    It is a failure of mine that I never got showing psuedo elements into the HTML panel of Firebug. Opera does it thought!

    0
  16. 25

    Hi, Referring to the code below. How do we make the image clickable, bringing us to another webpage? Please help. Thanks!

    a:before {
    content: url(link.png);
    padding-right: 0.2em;
    }

    0

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top