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?