Why do the :before and :after pseudo-elements require a 'content' property?
Asked Answered
A

5

29

Given the following scenario, why does the :after selector require a content property to function?

.test {
    width: 20px;
    height: 20px;
    background: blue;
    position:relative;
}
			
.test:after {
    width: 20px;
    height: 20px;
    background: red;
    display: block;
    position: absolute;
    top: 0px;
    left: 20px;
}
<div class="test"></div>

Notice how you do not see the pseudo element until you specify the content property:

.test {
    width: 20px;
    height: 20px;
    background: blue;
    position:relative;
}
			
.test:after {
    width: 20px;
    height: 20px;
    background: red;
    display: block;
    position: absolute;
    top: 0px;
    left: 20px;
    content:"hi";
}
<div class="test"></div>

Why is this the intended functionality? You would think that the display block would force the element to show up. Oddly enough, you can actually see the styles inside web debuggers; however, they do not display on the page.

Anecdotal answered 12/6, 2013 at 14:18 Comment(0)
D
26

The reason you need a content: '' declaration for each ::before and/or ::after pseudo-element is because the initial value of content is normal, which computes to none on the ::before and ::after pseudo-elements. See the spec.

The reason the initial value of content isn't an empty string but a value that computes to none for the ::before and ::after pseudo-elements, is twofold:

  1. Having empty inline content at the start and end of every element is rather silly. Remember that the original purpose of the ::before and ::after pseudo-elements is to insert generated content before and after the main content of an originating element. When there's no content to insert, creating an additional box just to insert nothing is pointless. So the none value is there to tell the browser not to bother with creating an additional box.

    The practice of using empty ::before and ::after pseudo-elements to create additional boxes for the sole purpose of layout aesthetics is relatively new, and some purists might even go so far as to call it a hack for this reason.

  2. Having empty inline content at the start and end of every element means that every (non-replaced) element — including html and body — would by default generate not one box, but up to three boxes (and more in the case of elements that already generate more than just the principal box, like elements with list styles). How many of the two extra boxes per element will you actually use? That's potentially tripling the cost of layout for very little gain.

    Realistically, even in this decade, less than 10% of the elements on a page will ever need ::before and ::after pseudo-elements for layout.

And so these pseudo-elements are made opt-in — because making them opt-out is not only a waste of system resources, but just plain illogical given their original purpose. The performance reason is also why I do not recommend generating pseudo-elements for every element using ::before, ::after.

But then you might ask: why not have the display property default to none on ::before, ::after? Simple: because the initial value of display is not none; it is inline. Having inline compute to none on ::before, ::after is not an option because then you could never display them inline. Having the initial value of display be none on ::before, ::after is not an option because a property can only have one initial value. (This is why the initial value of content is always normal and it is simply defined to compute to none on ::before, ::after.)

Dahliadahlstrom answered 7/3, 2017 at 16:21 Comment(4)
The other answers are good, but this should be the accepted answer. It answers the question completely.Glossolalia
The next question would be: Since the content property applies only to ::before and ::after pseudo-elements, then why is its initial value normal, which automatically computes to none? Why not cut out the extra step and just make the initial value none?Glossolalia
@Michael_B: I guess they were planning to make content work with real elements before ultimately deferring it to CSS3 (the propdef says "On elements, always computes to 'normal'." even though the property doesn't apply to elements to begin with). Making the initial value none means the entire page would disappear... until you add html, body, body * { content: normal } So, to allow for CSS3's definition it makes more sense for the initial value to be normal, which then computes to none for ::before, ::after since "normally" the pseudos aren't needed at all.Dahliadahlstrom
I changed this to the accepted answer. Thanks for the follow-up many years later.Anecdotal
R
28

Here are some references to various W3C specifications and drafts:

Selectors Level 3

The :before and :after pseudo-elements can be used to insert generated content before or after an element's content.

The :before and :after pseudo-elements

Authors specify the style and location of generated content with the :before and :after pseudo-elements. As their names indicate, the :before and :after pseudo-elements specify the location of content before and after an element's document tree content. The content property, in conjunction with these pseudo-elements, specifies what is inserted.

The content attribute

Initial: none

This property is used with the :before and :after pseudo-elements to generate content in a document. Values have the following meanings:

none - The pseudo-element is not generated.


The styling applied to ::before and ::after pseudo-elements affects the display of the generated content. The content attribute is this generated content, and without it present, the default value of content: none is assumed, meaning there is nothing for the style to be applied to.

If you don't want to repeat content:''; multiple times, you can override this simply by globally styling all ::before and ::after pseudo-elements within your CSS (JSFiddle example):

::before, ::after {
    content:'';
}
Raceway answered 12/6, 2013 at 14:55 Comment(9)
Thank you for the response. This is more along the lines of what I was looking for. However, can anyone think of a use case for using content: none over display: none? It seems like the specification is very odd for this property.Anecdotal
display:none could be used on these pseudo-elements to hide the generated content without overriding it. For example: counters which are only visible when an element is hovered over which don't have to be regenerated every time. Here's an updated Fiddle (hover over the text): jsfiddle.net/VzZUz/1Raceway
display: none on ::before and ::after has the exact same effect as content: none in any situation, including counters. If you're more comfortable using the display property to hide pseudo-elements, you can do that. Switching to the other property can also be useful if specificity is a problem.Dahliadahlstrom
I do not recommend globally enabling all ::before/::after pseudo-elements with unqualified selectors. If you have very complex pages this will result in the browser potentially tripling the number of boxes rendered - with the vast majority of generated-content boxes never being used. There is a reason the initial value of content is none and not the empty string...Dahliadahlstrom
... which is probably the answer Dave was looking for all along. See the comments under SLaks' answer. Do you want to add to your answer or shall I post a separate fleshed-out answer (notice I said "There is a reason" without elaborating)?Dahliadahlstrom
And now I'm getting massive déjà vu vibes - I feel like I've already once explained this elsewhere before... but all I can find is this answer that describes how old browsers used to treat content: ''.Dahliadahlstrom
I went ahead and posted an answer below.Dahliadahlstrom
@Dahliadahlstrom ah, sorry, I thought I'd replied to your message. I was going to say you may as well go ahead and answer it yourself as I've not much time for SO at the moment.Raceway
Ah, no worries!Dahliadahlstrom
D
26

The reason you need a content: '' declaration for each ::before and/or ::after pseudo-element is because the initial value of content is normal, which computes to none on the ::before and ::after pseudo-elements. See the spec.

The reason the initial value of content isn't an empty string but a value that computes to none for the ::before and ::after pseudo-elements, is twofold:

  1. Having empty inline content at the start and end of every element is rather silly. Remember that the original purpose of the ::before and ::after pseudo-elements is to insert generated content before and after the main content of an originating element. When there's no content to insert, creating an additional box just to insert nothing is pointless. So the none value is there to tell the browser not to bother with creating an additional box.

    The practice of using empty ::before and ::after pseudo-elements to create additional boxes for the sole purpose of layout aesthetics is relatively new, and some purists might even go so far as to call it a hack for this reason.

  2. Having empty inline content at the start and end of every element means that every (non-replaced) element — including html and body — would by default generate not one box, but up to three boxes (and more in the case of elements that already generate more than just the principal box, like elements with list styles). How many of the two extra boxes per element will you actually use? That's potentially tripling the cost of layout for very little gain.

    Realistically, even in this decade, less than 10% of the elements on a page will ever need ::before and ::after pseudo-elements for layout.

And so these pseudo-elements are made opt-in — because making them opt-out is not only a waste of system resources, but just plain illogical given their original purpose. The performance reason is also why I do not recommend generating pseudo-elements for every element using ::before, ::after.

But then you might ask: why not have the display property default to none on ::before, ::after? Simple: because the initial value of display is not none; it is inline. Having inline compute to none on ::before, ::after is not an option because then you could never display them inline. Having the initial value of display be none on ::before, ::after is not an option because a property can only have one initial value. (This is why the initial value of content is always normal and it is simply defined to compute to none on ::before, ::after.)

Dahliadahlstrom answered 7/3, 2017 at 16:21 Comment(4)
The other answers are good, but this should be the accepted answer. It answers the question completely.Glossolalia
The next question would be: Since the content property applies only to ::before and ::after pseudo-elements, then why is its initial value normal, which automatically computes to none? Why not cut out the extra step and just make the initial value none?Glossolalia
@Michael_B: I guess they were planning to make content work with real elements before ultimately deferring it to CSS3 (the propdef says "On elements, always computes to 'normal'." even though the property doesn't apply to elements to begin with). Making the initial value none means the entire page would disappear... until you add html, body, body * { content: normal } So, to allow for CSS3's definition it makes more sense for the initial value to be normal, which then computes to none for ::before, ::after since "normally" the pseudos aren't needed at all.Dahliadahlstrom
I changed this to the accepted answer. Thanks for the follow-up many years later.Anecdotal
P
9

Based on your comments on others' answers, I believe your question is actually:

Why must the content property for pseudo-classes be set in the CSS, as opposed to the content of non-pseudo-classes, which may be set in either HTML or CSS?

The reason is that:

  • by definition, pseudo-classes are dynamically created for every single element specified by a page's HTML markup
  • all page elements, including pseudo-classes, must have a content property to be displayed.
  • HTML elements like <p> do as well, but you can set their content property quickly using markup (or with CSS declarations).
  • Howver, unlike non-pseudo-class-elements, pseudo-classes cannot be given values in the markup itself.
    Therefore, all pseudo-classes are invisible (their 'content' properties have no value) unless you tell them not to be (by giving them value with CSS declarations).

Take this simple page:

<body>
<p> </p>
</body>

We know this page will display nothing, because the <p> element has no text. A more accurate way to rephrase this, is that the <p> element's content property has no value.

We can easily change this, by setting the content property of the h1 element in the HTML markup:

<body>
<p>This sentence is the content of the p element.</p>
</body>

This will now display when loaded, because the content property of the <p> element has a value; that value is a string:

"This sentence is the content of the p element."

Alternatively, we can cause the <p> element to be displayed by setting the content property of the <p> element in the CSS:

p { content: "This sentence is the content of the p element set in the CSS."; }

These two ways of injecting the string into the <p> element are identical.

Now, consider doing the same thing with pseudo-classes:

HTML:
  <body>
      <p class="text-placeholder">P</p>
  </body>

CSS:
  p:before { content: "BEFORE... " ; }
  p:after { content: " ...and AFTER"; }

The result:

BEFORE...  P ...and AFTER

Finally, imagine how you would accomplish this example without using CSS. It's impossible, because there is no way to set the content of the pseudo-class in the HTML markup.

You might be creative and imagine something like this might work:

<p:before>BEFORE... </p>
<p> P </p>
<p:after> ...and AFTER</p>

But, it doesn't, because <p:before> and <p:after> are not HTML elements.

In conclusion:

  • Pseudo classes exist for every markup element.
  • They are invisible by default, because they are initialized with no content property.
  • You CANNOT set the content property for pseudo classes with HTML markup.
    Therefore, pseudo-elements' content property must be declared with CSS declarations, in order to be displayed.
Pennington answered 14/2, 2014 at 20:15 Comment(1)
This answer was very confusing on first read and I had to do a second pass with all occurrences of "pseudo-class" replaced with "pseudo-element" (and all existing uses of "pseudo-element" intact). Please note that these two terms are not interchangeable.Dahliadahlstrom
A
6

Until you add content: ..., the psuedo-element doesn't actually exist.

Setting other style properties is not enough to force the browser to create the element.

Aubarta answered 12/6, 2013 at 14:20 Comment(5)
But why have this requirement at all? Doesn't the existence of an :after selector show the need for an element?Anecdotal
@DaveC The value of the content property is where you enter the content to be added as a generated element. If there is to content, there is no need to generate an element.Stately
I strongly disagree. With that logic, adding content: "" would cause :after tags to disappear as well... which, in fact, makes it show up.Anecdotal
@DaveC Sorry, I mis-stated. If there is no content property, then there is no need to generate a pseudo element. As you noted, a pseudo element can have an empty string as a value.Stately
I fully understand that it was implemented that way... maybe I should rephrase my question... why doesn't the specification assume that when a psuedo element is defined that is has content: ""? Is there any pro to assuming a missing element? Especially when any psuedo element without a content is a completely dead style. I suppose you could leave it hidden and add content: "" on later with a different style; however, this behavior can be achieved with display: none.Anecdotal
S
2

2020 Edit

:before

Is syntax from CSS2 for better practices you should write the newest syntax from CSS3 which is

::before

with double semicolons

Full answer and differences can be found here: What is the difference between :before and ::before?

Scourings answered 23/10, 2020 at 10:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.