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.
Further Reading on SmashingMag:
- An Ultimate Guide To CSS Pseudo-Classes And Pseudo-Elements
- Learning To Use The :before And :after Pseudo-Elements In CSS
- !important CSS Declarations: How and When to Use Them
- CSS Specificity And Inheritance
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 thebackground-image
property.counter()
,counters()
These functions insert counters (see below for details).attr(attribute)
This function enables us to insert the value ofattribute
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(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:
- increments the second counter (
definition
) ondd
elements; - inserts the first counter (
term
), followed by a period; - 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:
- Its first argument is the name of the given counter, immediately followed by a comma.
- 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
- “Learning to Use the :before and :after Pseudo-Elements in CSS,” Louis Lazaris, Smashing Magazine
- “Styling Elements With Glyphs, Sprites and Pseudo-Elements,” Thierry Koblentz, Smashing Magazine
Source of image on front page: Riebart