Why I Stopped Writing :after and You Should Too
Tags: css, webdev, beginners
Published: 2022-04-01 16:48:34

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 of `: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](https://www.w3.org/TR/CSS21/generate.html#before-after-content)' 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](https://www.w3.org/TR/2011/REC-css3-selectors-20110929/). 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:

{% codepen https://codepen.io/shimin-zhang/pen/XWVeZGd %}

### 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 selectors like `::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.

{% codepen https://codepen.io/shimin-zhang/pen/LYejLJq %}

### 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.

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!

{% codepen https://codepen.io/shimin-zhang/pen/NWXvWyR %}

If you liked this post, you may also be interested in learning about how to create [Style APIs using CSS Custom Properties](https://blog.shimin.io/css-custom-properties-as-style-api/) or when you should use the [CSS px unit](https://blog.shimin.io/three-questions-to-ask-when-using-css-px/) in 2022.