Given the following scenario, why does the :after
selector require a content property to function?
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:
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.
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
.)
Here are some references to various W3C specifications and drafts:
The
:before
and:after
pseudo-elements can be used to insert generated content before or after an element's content.
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. Thecontent
property, in conjunction with these pseudo-elements, specifies what is inserted.
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:'';
}
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:
<p>
do as well, but you can set their content property quickly using markup (or 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:
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?
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.