Why is :hover:after valid but not :after:hover? CSS Pseudo-Class and Pseudo-Element Selectors
The other day, I tried to add an underline style to an element on hover and it was not working. I did not realize I was styling a pseudo-element created by :after
. I preferred the single-colon variation :after
. It's as valid as ::after
, life is short, and I am a lazy developer. My :after:hover
didn't work and I got frustrated.
Troubleshooting the issue got me thinking, can I combine other pseudo-elements like ::first-letter
and ::first-line
? And if not, why? And why are both :after
and ::after
legal anyway?
In this post, I will briefly go over the history of CSS pseudo-element and pseudo-class selectors, how they differ, how to combine them in everyday usage, and why I am an ::after
only guy now.
Why is :after Valid CSS?
First, let's go over some CSS history. I first learned CSS in the early 2010s, when CSS 2.1 was 'The Standard' and single-colons roamed the land. While CSS 1 specified pseudo-class selectors like :link
and :visited
, it is CSS 2 that introduced pseudo-elements like :before
and :after
.
Back then, there was less emphasis on a pseudo-element vs pseudo-class differentiation – they both used the single-colon syntax. This is also when I made the mistake of conflating the two into a single pseudo-selector concept in my mind.
The two-colon syntax for pseudo-elements was introduced in CSS Selector 3. Due to the web's compatibility tenet, pseudo-element selectors from CSS 2.1 – including :after
, :before
, :first-line
and :first-letter
– are still valid today. However, new pseudo-elements like ::selection
do not have a corresponding single-colon version – see for yourself by selecting each line in the below example:
See the Pen Untitled by Shimin Zhang (@shimin-zhang) on CodePen.
Difference Between Pseudo-Class and Pseudo-Element
Pseudo-Class and Pseudo-Elements are both CSS selectors that have access to information outside of what the HTML DOM provides – hence the 'pseudo' prefix. Take the :hover
selector, it needs to know the user's current cursor location.
While pseudo-elements act on a DOM-like object, they create a pure CSS construct and do not actually affect the DOM. They also have an additional colon to denote this difference. The fact that ::after
element is not an actual DOM node has accessibility implications, their content does not exist for screen readers and should be decorative only.
Readers thinking 'what about :disabled
and :nth-child()
?' – you got me. The previous two paragraphs aren't strictly true, some pseudo-class selectors are more 'tricks' or 'shortcuts' – I wish W3C would create a new name for those to make the distinction clear-cut.
To repeat, the conceptual difference between the two is that pseudo-class selectors act on an existing DOM element using some outside information while pseudo-element creates a new, 'not actually there' element for the render engine only. The :visited
pseudo-selector can only be used to select to existing anchor elements. While ::first-line
create elements that otherwise cannot exist with the given DOM structure – hence the name.
Here's an example, the ::first-line
pseudo-element is created by splitting the second <span>
element into two in order to change the color of its first word only.
See the Pen Untitled by Shimin Zhang (@shimin-zhang) on CodePen.
How to Combine Pseudo-Class with Pseudo-Elements
What does knowing the difference between pseudo-classes and pseudo-elements tell us about their usage?
For one, pseudo-class selectors act on elements and can be chained just like any actual .class
selectors. :visited:hover
is as valid as .visited.hover
.
On the other hand, pseudo-element selectors cannot be used interchangeably from an element
selector. You cannot chain them. Pseudo-elements are not actual elements – they cannot be a selector's target. p::first-line::first-letter
is not valid because ::first-line
is not a real DOM element. Only use pseudo-elements at the end of a selector line.
As with all things CSS, the above is not an iron-clad rule. ::prefix
and ::postfix
of ::first-letter
are currently in the editor's draft as modifications on the pseudo-element, and may eventually be officially included. However, these pseudo-element stacks should be treated as the exception and not the rule.
The same logic applies when combining pseudo-class and pseudo-element selectors. Pseudo-class results in DOM elements, so pseudo-elements can be added – why p:hover:after
is valid CSS. Switch the order, and we are applying pseudo-class to a non-existing element – why p:after:hover
is invalid.
Sidebar, if you enjoyed reading this article so far, please share it with others. If you really enjoyed it and don't want to miss the next one, you can sign up below to get new posts in your inbox weekly.
Here are some rules to remember next time you find yourself using the pseudo-selectors:
Rule 1: Pseudo-classes can be used like actual .class selectors and used freely. p:hover.class works.
Rule 2: Pseudo-elements should always be at the very end of your selector, they cannot be chained because they do not select real DOM elements.
Rule 3: Do not use the single-colon variation of pseudo-elements – even if it's easier – it's too easy to slip up when they share the same syntax. Plus, you now have to remember that :after
works but not :selection
.
Before I go, here's a Code Pen with some pseudo-class and pseudo-element examples. Use it to test your knowledge. Read the CSS for each div and see if its behavior matches your expectation!
See the Pen Pseudo Elements and Pseudo Selectors by Shimin Zhang (@shimin-zhang) on CodePen.
If you liked this post, you may also be interested in learning about how to create Style APIs using CSS Custom Properties or when you should use the CSS px unit in 2022.