Here’s a container with some child elements:
<div class="container">
<div>item</div>
<div>item</div>
<div>item</div>
</div>
If I do:
.container::before {
content: "x"
}
I’m essentially doing:
<div class="container">
[[[ ::before psuedo-element here ]]]
<div>item</div>
<div>item</div>
<div>item</div>
</div>
Which will behave just like a child element mostly. One tricky thing is that no selector selects it other than the one you used to create it (or a similar selector that is literally a ::before
or ::after
that ends up in the same place).
To illustrate, say I set up that container to be a 2×3 grid and make each item a kind of pillbox design:
.container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 0.5rem;
}
.container > * {
background: darkgray;
border-radius: 4px;
padding: 0.5rem;
}
Without the pseudo-element, that would be like this:
If I add that pseudo-element selector as above, I’d get this:
It makes sense, but it can also come as a surprise. Pseudo-elements are often decorative (they should pretty much only be decorative), so having it participate in a content grid just feels weird.
Notice that the .container > *
selector didn’t pick it up and make it darkgray
because you can’t select a pseudo-element that way. That’s another minor gotcha.
In my day-to-day, I find pseudo-elements are typically absolutely-positioned to do something decorative — so, if you had:
.container::before {
content: "";
position: absolute;
/* Do something decorative */
}
…you probably wouldn’t even notice. Technically, the pseudo-element is still a child, so it’s still in there doing its thing, but isn’t participating in the grid. This isn’t unique to CSS Grid either. For instance, you’ll find by using flexbox that your pseudo-element becomes a flex item. You’re free to float your pseudo-element or do any other sort of layout with it as well.
DevTools makes it fairly clear that it is in the DOM like a child element:
There are a couple more gotchas!
One is :nth-child()
. You’d think that if pseduo-elements are actually children, they would effect :nth-child()
calculations, but they don’t. That means doing something like this:
.container > :nth-child(2) {
background: red;
}
…is going to select the same element whether or not there is a ::before
pseudo-element or not. The same is true for ::after
and :nth-last-child
and friends. That’s why I put “kinda” in the title. If pseudo-elements were exactly like child elements, they would affect these selectors.
Another gotcha is that you can’t select a pseudo-element in JavaScript like you could a regular child element. document.querySelector(".container::before");
is going to return null
. If the reason you are trying to get your hands on the pseudo-element in JavaScript is to see its styles, you can do that with a little CSSOM magic:
const styles = window.getComputedStyle(
document.querySelector('.container'),
'::before'
);
console.log(styles.content); // "x"
console.log(styles.color); // rgb(255, 0, 0)
console.log(styles.getPropertyValue('color'); // rgb(255, 0, 0)
Have you run into any gotchas with pseudo-elements?
What is relatively useful in this behaviour is that you can easily center (or, in general, position) your pseudelements within the parent by adding
display: flex;
to the parent. For example while adding custom markers to<li>
elements. If the list item is a flexbox then its::before
pseudoelement and text content will line up as they should.This is also something worth mentioning: those pseudoelements within grid and flexboxes they are treated exactly the same way as text nodes. After all, you can have a structure like
Text node Another text node