Understanding the CSS ‘content’ Property

Gajendar Singh

If you are a front-end developer there is a good chance that you have heard about pseudo-elements as well as CSS’s content property. I won’t discuss pseudo-elements in depth here, but I suggest you take a look at this Louis Lazaris’ article on Smashing Magazine if you are unfamiliar with the concept or need a quick refresher.

In this article, we’ll focus on the content property. CSS’s content property works with the ::before and ::after pseudo-elements (which can use either single- or double-colon synax). The property is used to insert generated content in a web page and it is fully supported in all major browsers.

Basic Syntax for the content Property

The syntax for the content property is broken down as follows, with each of the values represented:

p::before {
  content: normal|counter|attr|string|open-quote|url|initial|inherit;

The CSS differs slightly from value to value. For example, to use the attr() value with ::before or ::after, you need to write CSS like the following:

p::after {
  content: " (" attr(title) ")";

That’s just one example, and more on that later. In the following sections I will discuss each value, including attr().

Value: none or normal

When set to none, the pseudo-element is not generated. If you set it to normal it computes to none for the ::before and ::after pseudo-elements.

p::before {
  content: normal;

p::after {
  content: none;

This kind of thing might be used in nested elements that already have a pseudo-element defined but you want to override the pseudo-element in certain contexts.

Value: <string>

This value sets the content to a string and can be any text content. If using non-latin characters, the characters need to be encoded. Let’s look at examples of each. Consider the following HTML:

<h2>Tutorial Categories</h2>
  <li>HTML and CSS</li>
  <li class="new">Sass &amp; Less</li>

<p class="copyright">SitePoint, 2015<p>

And then the following CSS:

.new::after {
  content: " New!";
  color: green;

.copyright::before {
  content: "\00a9  ";

Here we are inserting text content into one of the list items, and also inserting an encoded character (in this case, the copyright symbol) into the paragraph element.

See the Pen Content Property with Text by SitePoint (@SitePoint) on CodePen.

A string value must have quotes surrounding it and these can be either single or double quotes.

Value: <uri>

The <uri> value comes in handy when you are interested in displaying some sort of media, which you can do by pointing to an external resource (for instance an image). If for some reason the resource or image can’t be displayed, it is either ignored or some placeholder takes its place. Let’s look at some code that demonstrates the use of this value.

Here is the HTML:

<a class="sp" href="https://www.sitepoint.com/">SitePoint</a>

And this is the CSS to show SitePoint’s favicon along with some text:

.sp::before {
  content: url(https://www.sitepoint.com/favicon.ico);

See the Pen Content Property with url() by SitePoint (@SitePoint) on CodePen.

Value: counter() or counters()

This value is the most complex value that can be used with the content property. It is written as one of two different functions — counter() or counters(). For a more thorough discussion of CSS counters, check out this article. But here is a brief overview.

In the case of the first function, counter() , the generated text is the value of the innermost counter of the name that you specify in scope at this pseudo-element. It is formatted in decimal by default but can also be formated in roman numerals.

The other function, counters(name, string), is similar but represents all counters with the specified name (the first parameter) in the order from outermost to innermost. All these values are separated by the specified string (the second parameter). If you specify the name for the counter variable as none, inherit, or initial the declaration is ignored.

Here is an example to illustrate how to use a counter:

<h2>Name of First Four Planets</h2>
<p class="planets">Mercury</p>
<p class="planets">Venus</p>
<p class="planets">Earth</p>
<p class="planets">Mars</p>

And here is the CSS:

.planets {
  counter-increment: planetIndex;

.planets::before {
  content: counter(planetIndex) ". ";

This will number the items with generated content, similar to an ordered list. In this case, we could have easily used an ordered list. These types of counters are much handier when the items being numbered are interspersed among other elements.

Here is a demo that uses CSS counters with the counter() function in use.

Value: attr()

As shown earlier, the attr() function will insert the value of the specified attribute, which is the sole parameter. If the concerned element has no attribute, an empty string is returned.

Here’s an example:

  <li><a href="https://www.sitepoint.com/html-css/">HTML and CSS</a></li>
  <li><a href="https://www.sitepoint.com/javascript">JavaScript</a></li>
  <li><a href="https://www.sitepoint.com/mobile/">Mobile</a></li>

With the above HTML, the CSS below will show the value of the href attribute in parentheses next to the linked text:

a::after {
  content: " (" attr(href) ") ";

See the Pen Content Property using attr() by SitePoint (@SitePoint) on CodePen.

This trick is often used in print style sheets, to allow links to be visible in printed web pages.

Value: open-quote or close-quote

When set to one of these values, the content property generates an opening or closing quotation mark. It’s customarily used along with the <q> element, but you can use it with any element. So you might do something like the following:

blockquote::before {
  content: open-quote;

blockquote::after {
  content: close-quote;

See the Pen Content Property with open/close quotes by SitePoint (@SitePoint) on CodePen.

The close-quote value will work only with the ::after pseudo-element (for obvious reasons) and it will not produce anything if a value of open-quote is not also present on the same element using ::before.

Value: no-open-quote or no-close-quote

The no-open-quote value removes the opening quote from the specified element and the no-close-quote removes the closing quote. You might wonder how these values are useful. Look at the following HTML:

<p><q>A wise man once said: <q>Be true to yourself,
but don't listen to those who say <q>Don't be true to 
yourself.</q></q> That is good advice.</q></p>

Take note that in the above paragraph, the speaker is quoting someone (“A wise man…”) who, in turn, is also quoting others (“those who say…”). So we have quotes nested 3 levels deep. Grammatically, there is a correct way to handle this. If using generated content, here is how we can ensure the quotes are nested correctly:

q {
  quotes: '“' '”' '‘' '’' '“' '”';

q::before { 
  content: open-quote;

q::after {
  content: close-quote;

The first selector defines the kinds of quotes we want to use, three levels deep, using the quotes property. Then we insert the quotes as content using pseudo-elements. This is similar to what we did in the previous section.

See the Pen Content property with nested quotes by SitePoint (@SitePoint) on CodePen.

But what if we want the second level of quotes to be ignored and not inserted, for whatever reason? We can use the no-open-quote and no-close-quote values to override them:

.noquotes::before {
  content: no-open-quote;

.noquotes::after {
  content: no-close-quote;

In this case, I’ve added a class of noquotes to the second level quote. This ensures that the quote nesting is still recognized, but the quotes don’t appear for that element. So the third level quote in that paragraph will have double curly quotes around it, rather than single curly quotes.

See the Pen Content property with no-open-quote/no-close-quote by SitePoint (@SitePoint) on CodePen.


I hope this tutorial helped you to understand each of the values of the content property a little better and how they can be used in various scenarios.

Check out the resources below for more info: