Briefly
Pseudo-elements are elements that do not exist in the HTML markup. They are created and positioned exclusively through CSS. They are most often used to create various decorative elements (which do not carry meaningful content).
Pseudo-elements also come in handy when you need to overlay an image with so-called overlay (a covering layer). The usefulness of pseudo-elements does not end there.
Example
The most common scenario for using a pseudo-element is to overlay an image with a semi-transparent fill. This is often required on the first screen, but this method can overlay on any image on the site.
<header class="header"> <h1 class="header__title">Good evening, Clarice.</h1></header>
<header class="header"> <h1 class="header__title">Good evening, Clarice.</h1> </header>
.header { background: #999999 url("background.svg") no-repeat center / cover;}.header__title { color: #ffffff; font-size: 82px; text-transform: uppercase; text-align: center;}
.header { background: #999999 url("background.svg") no-repeat center / cover; } .header__title { color: #ffffff; font-size: 82px; text-transform: uppercase; text-align: center; }
To make the image color less bright and make the text more readable, we will add a semi-transparent black fill over the entire header
.
We add positioning to the parent and specify the stacking order:
.header { position: relative; z-index: 0;}
.header { position: relative; z-index: 0; }
content
is a mandatory property for pseudo-elements :
and :
. We position the pseudo-element relative to the parent and stretch it to full width and height. After that, we set a semi-transparent black background and remove it under the text using z
:
.header::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0 0 0 / 0.7); z-index: -1;}
.header::before { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0 0 0 / 0.7); z-index: -1; }
Without touching the HTML markup, we added another element for decorative purposes. The markup remained clean. The pseudo-element is easy to manage, add, or remove as needed, even if there is no access to HTML.
How to Understand
The definition of the prefix "pseudo" in the dictionary: "… A prefix indicating the falseness or non-existence of what follows it."
From this definition, it can be understood that pseudo-elements do not actually exist. They are a creation of CSS that can be modified, deleted, or added without touching the real HTML markup.
How to Write
There are several pseudo-elements. Let's consider each of them.
::before
and ::after
The two most commonly found pseudo-elements. They are very similar. The only difference is that :
by default is placed before the content of the element for which it is set, while :
- after. This difference is reflected in the name: the word before translates from English as "before"; the word after translates as "after".
For both elements, the property content
is mandatory — the content of the pseudo-element. With it, for example, you can insert a word before or after the text. Without the content
property, the pseudo-element will not be displayed.
Let's create a text element with a username.
<span class="incoming">Gordon</span>
<span class="incoming">Gordon</span>
Let's greet the user and pay him a compliment:
.incoming::before { content: "Hello, ";}.incoming::after { content: ", you look great!";}
.incoming::before { content: "Hello, "; } .incoming::after { content: ", you look great!"; }
As seen in the example, the text from the content
property of the :
pseudo-element appeared before the username, while from the :
pseudo-element it appeared after.
These pseudo-elements are inline by default. But this can be easily changed with styles.
Let's underline the text with a decorative line.
We will set the parent to display inline-block so that the line is the width of the text:
.incoming { display: inline-block;}.incoming::before { content: "Hello, ";}.incoming::after { content: ""; display: block; width: 100%; height: 2px; background-color: #F498AD;}
.incoming { display: inline-block; } .incoming::before { content: "Hello, "; } .incoming::after { content: ""; display: block; width: 100%; height: 2px; background-color: #F498AD; }
::first-letter
This pseudo-element allows you to select the first letter in a line or paragraph of text. This allows you to create what is called a drop cap — a large decorative letter at the start of the text.
<p class="text"> In the grass, near the high anthills...</p>
<p class="text"> In the grass, near the high anthills... </p>
We increase the size of the first letter and paint it red:
.text::first-letter { font-size: 52px; color: #F498AD;}
.text::first-letter { font-size: 52px; color: #F498AD; }
::first-line
This pseudo-element selects the first line of text. Note that it will only work for block elements. This trick will not work with inline elements.
For example, you can create a "red line," but not as taught in school =)
<p class="text"> In a distant and pale depth of the sky...</p>
<p class="text"> In a distant and pale depth of the sky... </p>
We will paint the first line red:
.text::first-line { background-color: #F498AD;}
.text::first-line { background-color: #F498AD; }
::selection
With the :
pseudo-element, you can control the style of the text that the user selects with the mouse.

The text on the site Smashing Magazine is highlighted in red. Using the :
pseudo-element allows maintaining the design throughout, right down to such a small detail as text selection.
It is most reasonable to apply this pseudo-element not to any specific block, but to the entire page.
::selection { background-color: #F498AD;}
::selection { background-color: #F498AD; }
When selecting text in this example, it will be seen that the selection background is bright red.
::placeholder
It allows styling the hint that is displayed in the text input field (<input>
).
The hint text is set using the placeholder
attribute on the <input>
tag.
<input type="email" placeholder="test@example.com">
<input type="email" placeholder="test@example.com">

By default, the hint text color is gray. But sometimes the design requires a different color.
Use the :
pseudo-element and set the necessary styles for the hint. You can change everything, even the font. At the same time, the styles for the text that the user will enter will not be affected.
input::placeholder { color: #2E9AFF;}
input::placeholder { color: #2E9AFF; }
::marker
:
is the pseudo-element responsible for the marker field. It contains, for example, list markers.
::backdrop
The :
pseudo-element is responsible for the background that appears behind pop-up elements at the top level.
Tips
💡 Be sure to check the support of the pseudo-element in the necessary browsers. You can use the site Can I use.
💡 For the content
property, there are several useful tricks. For example, you can use a data attribute on a tag (which can be set using JavaScript) and the value attr
to display the number of unread messages on a label. Or the number of items in the cart. This method would be "cheaper" in terms of not altering the real HTML markup.
💡 As a value for the content
property, you can insert Unicode. For example, you can add the copyright symbol using the notation content
.
💡 You may encounter in the code the writing of pseudo-elements with a single colon at the beginning. This syntax is almost always acceptable, except for the pseudo-elements :
and :
. However, in the latest versions of the specification, it is recommended to write all pseudo-elements with two colons to visually distinguish them from pseudo-classes.
In practice
Advice 1
🛠 Since the pseudo-elements :
and :
are not present in the DOM tree, it is impossible to attach a JavaScript event to them. In most cases, it is sufficient to track events on the element itself and use it to change the properties of the pseudo-element. If you need to track a click specifically on the pseudo-element, you can use a hack with offset
comparison.
For example, let's create a control that allows changing the quantity of items in the cart. In this case, the buttons “+” and “-” will be our pseudo-elements.
<span class="quantity-change">0</span>
<span class="quantity-change">0</span>
Let's set styles for the input field:
.quantity-change { display: flex; align-items: center; justify-content: space-between; width: 160px; height: 44px; border-radius: 6px; background-color: #2E9AFF; color: #000000; font-size: 24px; cursor: text; user-select: none;}
.quantity-change { display: flex; align-items: center; justify-content: space-between; width: 160px; height: 44px; border-radius: 6px; background-color: #2E9AFF; color: #000000; font-size: 24px; cursor: text; user-select: none; }
For this hack, it is important to closely monitor the width of the element, as the event will be calculated based on the width. In this case, the width of the element is 160 px.
The control elements (which will serve as :
and :
) will be 40 px:
.quantity-change::before, .quantity-change::after { display: flex; align-items: center; justify-content: center; width: 40px; font-size: 34px; cursor: pointer;}.quantity-change::before { content: '–';}.quantity-change::after { content: '+';}
.quantity-change::before, .quantity-change::after { display: flex; align-items: center; justify-content: center; width: 40px; font-size: 34px; cursor: pointer; } .quantity-change::before { content: '–'; } .quantity-change::after { content: '+'; }
Now, knowing the width of the elements and controls, we assume that clicking on the first 40 px is an event for :
, and clicking on the last 40 px is for :
:
const quantity = document.querySelector('.quantity-change')let counter = Number(quantity.textContent)quantity.addEventListener('click', (event) => { if(event.offsetX <= 40 && counter > 0) { counter-- } else if(event.offsetX >= 120) { counter++ } quantity.textContent = counter})
const quantity = document.querySelector('.quantity-change') let counter = Number(quantity.textContent) quantity.addEventListener('click', (event) => { if(event.offsetX <= 40 && counter > 0) { counter-- } else if(event.offsetX >= 120) { counter++ } quantity.textContent = counter })
Bingo! Now we can track clicks on the pseudo-elements and perform the necessary actions.
This method should be applied with caution, taking into account media expressions that can change the size of the element and break everything. If there is a possibility to design your code without the combination of “Pseudo-elements + JavaScript”, it would be a good solution.
Advice 2
🛠 Very interesting and useful trick — setting a custom counter for lists. Sometimes there is a need to add parentheses after the number instead of the standard dot.
<ol class="list"> <li class="list__item">Definition.</li> <li class="list__item">Properties.</li> <li class="list__item">Lists in programming languages.</li> <li class="list__item">See also.</li> <li class="list__item">Notes.</li></ol>
<ol class="list"> <li class="list__item">Definition.</li> <li class="list__item">Properties.</li> <li class="list__item">Lists in programming languages.</li> <li class="list__item">See also.</li> <li class="list__item">Notes.</li> </ol>
To create a custom counter, first, you need to reset the standard list markers:
.list { list-style-type: none;}
.list { list-style-type: none; }
Now let's come up with and write the name of the new counter. The counter's name will be used further.
.list { list-style-type: none; counter-reset: new-counter;}
.list { list-style-type: none; counter-reset: new-counter; }
We tell the browser that it needs to count the list items:
.list__item { counter-increment: new-counter;}
.list__item { counter-increment: new-counter; }
Now for each list item, we write a pseudo-element :
and start the custom counter, adding the necessary characters. In this case, we add an opening parenthesis and a space in quotes:
.list__item::before { content: counter(new-counter) ") ";}
.list__item::before { content: counter(new-counter) ") "; }
In a similar way, you can change the styles of markers as you see fit.
🛠 Pseudo-elements are the best thing that has been invented in CSS 😆 With them, it is much easier to live and layout. Spend some time searching for interesting tricks and features that can be done using pseudo-elements.
These little friends of a layout designer can surprise you with their power.
Their capabilities deserve a separate article. For example, one written by Chris Coyier: «A Whole Bunch of Amazing Stuff Pseudo Elements Can Do».