How To Benefit From CSS Generated Content And Counters

About The Author

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 … More about Gabriele ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

For several years, Generated content was used by relatively few Web authors due to inconsistent browser support. But in 2009, the feature was rediscovered, and many interesting implementations were adopted for the first time. In this article Gabriele Romanato will show us some possible uses of generated content.

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 content.

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 website.

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(https://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.

Other Resources

Source of image on front page: Riebart

Further Reading

Smashing Editorial (al, mrn)