Note the double-colon ::before
versus the single-colon :before
. Which one is correct?
Technically, the correct answer is ::before
. But that doesn’t mean you should automatically use it.
The situation is that:
- double-colon selectors are pseudo-elements.
- single-colon selectors are pseudo-selectors.
::before
is definitely a pseudo-element, so it should use the double colon.
The distinction between a pseudo-element and pseudo-selector is already confusing. Fortunately, ::after
and ::before
are fairly straightforward. They literally add something new to the page, an element.
But something like ::first-letter
is also a pseudo-element. The way I reason that out in my brain is that it’s selecting a part of something in which there is no existing HTML element for. There is no <span>
around that first letter you’re targeting, so that first letter is almost like a new element you’re adding on the page. That differs from pseudo-selectors which are selecting things that already exist, like the :nth-child(2)
or whatever.
Even though ::before
is a pseudo-element and a double-colon is the correct way to use pseudo-elements, should you?
There is an argument that perhaps you should use :before
, which goes like this:
- Internet Explorer 8 and below only supported
:before
, not::before
- All modern browsers support it both ways, since tons of sites use
:before
and browsers really value backwards compatibility. - Hey it’s one less character as a bonus.
I’ve heard people say that they have a CSS linter that requires (or automates) them to be single-colon. Personally, I’m OK with people doing that. Seems fine. I’d value consistency over which way you choose to go.
On the flip side, there’s an argument for going with ::before
that goes like this:
- Single-colon pseudo-elements were a mistake. There will never be any more pseudo-elements with a single-colon.
- If you have the distinction straight in your mind, might as well train your fingers to do it right.
- This is already confusing enough, so let’s just follow the correctly specced way.
I’ve got my linter set up to force me to do double-colons. I don’t support Internet Explorer 8 anyway and it feels good to be doing things the “right” way.
I believe experience already showed we could stick to single colon. Why not drop double colon and put an end to the confusion?
I agree! Why not drop the distinction altogether? Even if it was a mistake it is easier to remember than having to change the syntax based on such an abstract concept.
Totally agree – clearly one colon is all that is needed if they are interchangeable. Why make things harder than they have to be?
Couple of interesting(?) notes:
The
content: 'some string'
CSS rule only works on::before
and::after
, not any other pseudo-element.At least on Chrome,
::first-letter
selected everything up to and including the first letter, so for examplekbd::first-letter { color: red }
on<kbd>#!/usr/bin/python</kbd> would highlight
#!/u`.Works when display kbd as “
block
” element (small limitation for::first-letter
):kbd {
display: inline-block; /* display: block */
}
According to Mozilla “Punctuation that precedes or immediately follows the first letter is included in the match. Punctuation includes any Unicode character defined in the open (Ps), close (Pe), initial quote (Pi), final quote (Pf), and other punctuation (Po) classes.” (https://developer.mozilla.org/en-US/docs/Web/CSS/::first-letter)
This is a classic example where it seems there’s a clearly-correct way to write it (::before, for spec correctness / future-thinking) and deploy it (:before, support and that precious char count), and they’re opposing.
Build tools & deployment processes make it easy to flip a switch when CSS8 finally deprecates single-colon syntax :)
Pseudo-classes are selectors based on state, pseudo-elements are selectors based on content. It’s actually easier to remember when something is a pseudo class and just assume that if it doesn’t fit that definition, then it must be a pseudo-element.
Pseudo-classes can change based on a user action (or interaction).
:hover
being an easy example. It only takes that style when the user is hovering on the element. A trickier one might be:valid
since the content is what is valid or invalid, but this is expected to change as part of the user input. The input element itself isn’t invalid, it just as a value that is invalid, thus it has a state of not valid (and the style gets applied).I’m not sure why
::first-letter
would be confusing, but again, it can be determined to be not a pseudo-class by thinking “is this something that is expected to change based on user action?” Sure, there could be some fun JavaScript that makes a new paragraph when a user clicks a button, but this hasn’t changed the state of the “first letter”, it simply has made some other content the first letter. Since it doesn’t sound like a pseudo-class, it’s therefore a pseudo-element.Umm… your analogy works for the most part but what about
:first-child
?That isn’t really a state based selector. Unless if it is some sort of dynamic content, the user can’t change what the first-child element is.
Doble colon is technically the right way, however I would normally use single colon to try to extend support. Not that I’m actively trying to support IE8, but will not hurt.
As a sidenote, the distinction between pseudo-elements and pseudo-classes is crystal clear in most cases, but some are more complicated. Even browser devs can’t seem to agree.
Consider for instance placeholders. Webkit browsers use
::-webkit-input-placeholder
, so it’s a pseudo-element to them. Trident runs:-ms-input-placeholder
, which would mean they consider it a pseudo-class. And Gecko actually switch their pseudo-class:-moz-placeholder
to the pseudo-element::-moz-placeholder
in Firefox 19…Your last phrase is the best
What’s really going to do your head in is when you use
::before
, and then gulp-sass transpiles that to/before
– but only on the build server, not on anyone’s local development environment (all version matched). We’ve had to change back to single colon to avoid this bugMaybe I’m living in a fantasy land, but surely, surely IE8 support isn’t something we have to worry about?
Microsoft haven’t supposed any version of IE except 11 for several years.
Also IE 8 is well over 10 years old. That means it predates (for example) the entire Marvel Cinematic Universe.
Our main product involves working with confidential information. We don’t support any version of IE below 11 (and we’re sunsetting that), because they’re implicitly insecure, and only available on operating systems that are well past EOL.
I genuinely can’t think of a reason to keep a feature in just to support decade-old software, because I can’t fathom a valid reason to be using it. Can anyone give a reason?
We should write it as double-colon because this is the correct one – if we need to support single-colon then it should be added using for example autoprefixer (serious question – is autoprefixer support adding single-colon to browsers that don’t support double-colon version?) – the same situation that happens with gradients – we have three syntaxes but we should use only one (correct) – new and let autoprefixer add tweener and old syntaxes when needed.
Autoprefixer wouldn’t add
:before
. It’s not a prefix and they are pretty strict on that rule to keep scope creep down.Even if it did, you would explicitly have to declare in your config that you want to support IE8 in order for it to be included in your output CSS.
Can’t you look at it like this: you have the pseudo-element
::before
, that’s also what you see if you look at the code in e.g. Chrome Dev Tools. In CSS you select the pseudo-element::before
with the pseudo-selector:before
. So one colon…Aren’t they both called pseudo-selectors? And split up being pseudo-elements (double colon, ::before ::after) and pseudo-classes (single colon like :hover, :focus :disabled etc..) ?
I recently did a screencast for my company on this (not public yet). Similar to you, Chris, I made the conclusion that if the browser needs to “add stuff” in order to render the styles, it’s a pseudo-element (browser needs to “wrap the first letter in something” so we can apply
::first-letter
). If the styles are added to already existing elements, it’s a pseudo-class.I’ve advocated for a while that we should use the spec, but until doing that video, I didn’t have a valid definition of how the pseudos differ.
There are now pseudo-elements, such as
::placeholder
, that require two colons. Therefore, to be consistent, all pseudo-elements (including::before
) should also have two colons.The eternal battle of Specs vs Real world usage
I had browser compatibility issues when I used the double colon ::before, since then I never used it with double colon.
I’m not confused about the distinction, just confused as to why the distinction is necessary. The double colon has always seemed like a waste of a keystroke to me.
I have a CSS linter that requires (or automates) them to be single-colon. So, What can I Do?