A guide to CSS pseudo-elements
A guide to CSS pseudo-elements êŽë š
Editor's note
This post was updated on 29 September 2022 to include a comparison of CSS pseudo-classes and pseudo-elements, and working CodePen demos of each pseudo-element.
The basic CSS selectors and their various properties are fun to work with, but learning about pseudo-classes and pseudo-elements is the next step towards becoming a CSS expert.
As a frontend developer, you should have a working knowledge of CSS pseudo-elements, including their functionalities and their different presentational and structural applications.
This article covers a detailed overview of pseudo-elements, how they differ from pseudo-classes, their different types and use cases, new additions in the latest module.
What are CSS pseudo-elements?
A CSS pseudo-element is primarily a keyword added to a CSS selector that lets you style a specific part of the selected HTML element. It acts as a sub-element and provides additional functionality to the selected entity.
Pseudo-elements were first introduced in 2015 with a single colon syntax. The later modules from CSS3 use a double-colon pseudo-elements syntax as shown below:
/* Older way (CSS2) */
selector:pseudo-element {
property: value;
}
/* Modern way (CSS3 onwards) */
selector::pseudo-element {
property: value;
}
As shown above, the double-colon keywords are our pseudo-elements that also indicate their functionality by their names. We will go through each of them in the upcoming segments.
Pseudo-elements vs. pseudo-classes
A CSS pseudo-class is a state of the selected element when it goes through an event or series of events. You can change the element styles for a particular event with pseudo-classes.
In contrast, a CSS pseudo-element behaves like a sub-element itself and adds a different functionality to the selected element, based on its type.
Types of CSS pseudo-elements
There are some browser-specific and experimental pseudo-elements that weâll be covering in the later segments of this article. For now, here is a list of different pseudo-elements that modern browsers support:
::before
::after
::first-letter
::first-line
::marker
::placeholder
::selection
::backdrop
::file-selector-button
::cue
::part()
::slotted()
All the examples weâll be looking at in this tutorial are available in this codepen collection (JGzmbo
). You may also copy-paste the code and use it in your choice of code editor.
::before
The ::before
keyword creates a pseudo-element that appears just before the content of the selected HTML element.
By default, it has an inline display and needs to be provided with the CSS content property to function. Take a look at the code snippet below for the ::before
pseudo-elementâs syntax:
.pe-before::before {
content: "Content injected by the ::before pseudo-element.";
display: block;
margin-bottom: 1em;
}
The above code injects some content into each element with a pe-before
class by making use of the ::before
pseudo-element. See the demo here (_rahul
). The frontend development world often refers to this type of injected content as generated content.
Hereâs another example to demonstrate an advanced application of ::before
.
Letâs take a tag list as a use case where showing a tag icon or text just before the tag entity makes total sense.
Some alignment and spacing adjustments with flexbox properties and the ::before
pseudo-element makes it super easy to implement:
.tag-list {
display: flex;
gap: 1em;
}
.tag-list li,
.tag-list a {
display: block;
}
.tag-list a::before {
content: "#";
...
}
::after
The ::after
pseudo-element works just like ::before
except for the fact that instead of appearing before, it appears just after, the content of the selected HTML element.
Like ::before
, ::after
has an inline display by default and takes a content property to function. Hereâs an example to demonstrate its syntax:
.pe-before::after {
content: "Content injected by the ::after pseudo-element.";
display: block;
margin-top: 1em;
}
Check out the code in action here:
For an advanced implementation of the ::after
pseudo-element, consider constructing a âbreadcrumbâ navigation with forward-slashes between items.
Hereâs an easy way to achieve that with ::after
and some alignment adjustments:
::first-letter
Automatically targeting the first letter of a given text block can help create rich typographical enhancements like drop caps.
Doing so may sound tricky, but the ::first-letter
pseudo-element in CSS makes it relatively simple. It represents the first letter of the first line of a block element and only works if the first child of the targeted element is a text block.
Hereâs how easy it is to target the first letter of all the paragraph tags and make them appear a bit bolder in weight:
p::first-letter {
font-weight: 700;
}
Most magazine-based blog layouts implement the ::first-letter
pseudo-element to highlight the first letter of the first paragraph of an article. It is popularly known as the drop cap effect:
main p:first-child::first-letter {
text-transform: uppercase;
font-weight: 700;
font-size: 3em;
line-height: 1;
float: left;
margin: 0 0.5rem 0.1rem 0;
}
It is crucial to realize that the first letter of an element with ::first-letter
can sometimes be tricky to spot. For example, if a text contains a punctuation mark at the beginning, the pseudo-element will logically select the mark instead of the actual first letter.
Another example could be a digraph or a trigraph, where you would want to select all two or three letters. The ::first-letter
pseudo-element, in this case, could select only the first one.
Also, keep an eye on using ::before
with ::first-letter
. The ::before
pseudo-element acts as the first child of a given element. This will cause ::first-letter
to prefer the generated content added by it over actual content at the time of selection.
::first-line
With ::first-line
, we can easily select the first line of text in a block element. This pseudo-element considers the width and font size of the element, along with the width of the document to determine the selection:
p::first-line {
font-weight: 700;
}
The code above will make the first line of every paragraph element bolder in weight. See a quick demo of the same here:
Letâs take it a step further by changing the case for the first line of an article. This can be done by pairing ::first-line
with the :first-child
pseudo-class:
::marker
This pseudo-element enables us to select and modify the bullet icon and number in the marker box of a list item. It works with anything with a list-item
display. <li>
and <summary>
elements are some of its general applications:
li::marker {
content: "â„";
}
See the demo here:
As you can see above, the ::marker
pseudo-element made the ::before
pseudo-element unnecessary. It would be great if it allowed adding spacing between the marker and the text in the future.
Adding custom markers to an unordered list is a cakewalk. Letâs do something similar with disclosable summaries as well:
::placeholder
As the name suggests, the ::placeholder
pseudo-element allows you to style the placeholder of form input elements. Most browsers, by default, have light gray colored placeholders. Hereâs how we can modify them using the CSS code below:
input::placeholder {
color: blue;
}
In the demonstration below, I tried to make the color of the placeholder text look related to the background of the field. CSS HSL color function is handy for such cases; you can keep the hue constant and generate different shades and tints by only modifying lightness and saturation:
::selection
Generally, when you select text on a web page, the selection is highlighted in blue. The ::selection
pseudo-element enables you to customize the styles for this highlighting:
::selection {
background-color: #ccc;
color: #888;
}
You may hook it to the body or the root element to apply the changes to every element. The demonstration below shows its implementation with two different elements:
::backdrop
The ::backdrop
CSS pseudo-element represents a viewport-sized box rendered immediately beneath any element being presented in full-screen mode.
Letâs understand this with an example where we change the backdrop of a video from black to blue. Check out this CodePen example (_rahul
) and play the video in full-screen mode:
video::backdrop {
background-color: blue;
}
It works for the dialog elements as well, which, when initiated, get a customizable backdrop color. Click the âshow the dialogâ button in the demo below to see it in action:
::file-selector-button
The HTML file input element displays a button that seems impossible to style with CSS. To your surprise, the ::file-selector-button
pseudo-element actually lets you customize that button, and hereâs a working demonstration of that:
input[type="file"]::file-selector-button {
background-color: blue;
color: white;
...
}
You might not like specifying the input type when adding styles to this pseudo-element. Iâd recommend you always mention the type to keep things readable for others. See how neat the file input looks with some additions using ::file-input-button
:
::cue
The ::cue
CSS pseudo-element selects the WebVTT cues within a media element, usually videos. In simpler words, it allows you to style captions, subtitles, and other possible media cues using VTT tracks:
<style>
video::cue {
color: pink;
background-color: black;
}
</style>
<video controls src="./path/to/video.file">
<track default kind="captions" srclang="en" src="./path/to/vtt.file">
</video>
To avoid CORS issues, ensure that the video and VTT files are coming from the same domain. Here is what the ::cue
 powered VTT captions look like:
::part()
Due to its element encapsulation nature, the shadow DOM stays isolated from the rest of the page. Therefore, not all styles reach the components attached to the regular DOM from the shadow DOM.
The ::part()
pseudo-element, a new addition to CSS pseudo-elements, makes it possible to style the shadow DOM to a certain extent:
<template id="my-widget">
<div part="widget">
<p>...</p>
</div>
</template>
Assigning a âpartâ plays an important role here and will help us later to alter the above component using the ::part()
pseudo-element.
The code block below shows how it acts like a function and takes the part
as an argument:
my-widget::part(widget) {
...
}
Hereâs a small implementation of everything we discussed about ::part()
:
::slotted()
The slots in the shadow DOM are placeholders that hold the content between your custom web component tags. One limitation with slots is that you canât style them from within the shadow DOM tree.
The introduction of the slotted pseudo-element counters that issue by taking slot elements as arguments to add styles. Here is a simple implementation of ::slotted
using the same example as above:
<template id="my-widget">
<style>
div ::slotted(span) {
color: red;
}
section ::slotted(span) {
color: green;
}
</style>
<div>
<slot name="div">This is a div slot</slot>
</div>
<section>
<slot name="section">This is a section slot</slot>
</section>
</template>
Browser-specific pseudo-elements
Itâs impractical to use a browser-specific pseudo-element in production, because it will only add to the inconsistencies if no other alternatives are available for other browsers.
Some popular ones from such pseudo-elements are ::-moz-appearance
, ::-webkit-appearance
, and ::-webkit-search-cancel-button
. The appearance pseudo-elements are used to control the native appearance of UI controls based on the operating systemâs theme.
Web developers have been using the ::webkit-search-cancel-button
for years to hide that ugly blue-colored âcancelâ search control from HTML search inputs on WebKit-based browsers:
Experimental pseudo-elements
Currently, some CSS4 pseudo-elements are still in experimental mode, meaning they are still undergoing development and wonât work as expected in any browser.
There are mainly four pseudo-elements that fall into this category:
::target-text
: If the browser supports scroll-to-text fragments, the::target-text
CSS pseudo-element will allow us to highlight the scroll targets::spelling-error
: It will represent text segments flagged as grammatically incorrect by the user agent::grammar-error
: It will represent text segments flagged as incorrectly spelled by the user agent::cue-region
: Different from::cue
, it will be used to style the whole cue region instead of just the cue text
Conclusion
In the contents above, we learned about CSS pseudo-elements and their different applications. We also introduced some experimental and browser-specific pseudo-elements, and why it is currently not practical to use them in production.
Even though pseudo-elements add a lot of functionalities that you would be adding with JavaScript otherwise, avoiding their aggressive use will keep your layouts lighter and glitch-free.
I hope you added a bit more to your CSS knowledge with this tutorial. Let me know your thoughts in the comments.