skip to content

What is the difference between the CSS selectors :before and ::before, and which pseudo-elements refuse the single-colon form?

level: juniorimportance: should knowfreq 52%

answer

  1. one family used to look like the other
  2. punctuation added to disambiguate, not to change matching
  3. only the four originals kept the old spelling
  4. anything specified later is strict
  5. one bad selector drops the whole rule

basics

~20 s

Both forms select the same generated box. Double colon is the modern syntax that distinguishes pseudo-elements from pseudo-classes; single colon survives only for the four CSS2 pseudo-elements (::before, ::after, ::first-line, ::first-letter). Anything newer requires two colons.

solid answer

~40 s

They are equivalent for `::before`. CSS2 wrote every pseudo-element with one colon, exactly like pseudo-classes such as `:hover`. Selectors Level 3 introduced the double colon so the two families are visually distinguishable, and browsers kept the single-colon spelling for the four original pseudo-elements — `::before`, `::after`, `::first-line`, `::first-letter` — purely for legacy compatibility. Every pseudo-element specified afterwards is double-colon only: `::marker`, `::selection`, `::placeholder`, `::backdrop`, `::file-selector-button`. Writing `:placeholder` matches nothing. Worse, an unrecognised selector invalidates the whole rule it appears in, so grouping a valid selector with a bogus one silently drops both — a real hazard left over from the era of prefixed placeholder selectors. Modern code should always use two colons.

code

css · 8 lines
css
/* interchangeable: the four CSS2 pseudo-elements */
.quote:before  { content: open-quote; }
.quote::before { content: open-quote; }

/* double colon only: nothing here has a legacy alias */
input::placeholder { color: #888; }
li::marker         { color: crimson; }
::selection        { background: yellow; }

go deeper

for a junior

Say plainly that ::before and :before select the same thing and that you write two colons today. Know that only the four oldest pseudo-elements accept one colon.

for a middle

Explain the reason for the split: CSS2 used one colon for both families, and Selectors Level 3 added the second colon so pseudo-elements read differently from pseudo-classes. Name the double-colon-only ones.

for a senior

Bring up the invalidation hazard — an unknown selector kills the entire comma-separated rule — and explain why splitting rules or using a forgiving list avoids silently losing styles.

for a principal

Decide the house rule and make it enforceable by a linter, so nobody reintroduces mixed spellings or vendor-prefixed placeholder lists that can drop a working rule without any console warning.

## Two families, one punctuation mark CSS has two kinds of selector that attach to an element with a colon: - **Pseudo-classes** match an element in a particular *state* or structural position: `:hover`, `:checked`, `:first-child`. - **Pseudo-elements** address a *rendered sub-part* of an element that has no element of its own: the generated boxes, the first line, the list marker, the placeholder text. CSS2 spelled both with a single colon, so `:hover` and `:before` looked identical in shape despite doing categorically different things. Selectors Level 3 fixed the ambiguity by giving pseudo-elements a double colon: `::before`. ## Why the old spelling still works The change was cosmetic, and by the time it landed the single-colon spelling was baked into a large amount of deployed CSS. Browsers therefore continue to accept the legacy form for exactly the four pseudo-elements that CSS2 defined: ```css p:first-letter { font-size: 2em; } /* legacy, still matches */ p::first-letter { font-size: 2em; } /* modern, identical result */ ``` The pairs are interchangeable: `:before`/`::before`, `:after`/`::after`, `:first-line`/`::first-line`, `:first-letter`/`::first-letter`. Historically the single-colon form mattered because Internet Explorer 8 understood only that spelling, which is the reason so much older CSS uses it. That constraint is long gone. ## Everything newer is double-colon only Pseudo-elements specified after Selectors 3 have no legacy alias at all: ```css ::marker { color: crimson; } ::selection { background: yellow; } ::placeholder { color: #888; } ::backdrop { background: rgb(0 0 0 / 0.6); } ::file-selector-button { border-radius: 0; } ``` Writing `:marker` or `:placeholder` produces a selector the browser does not recognise. It does not fall back — it simply never matches. ## The invalidation trap A plain selector list is **not forgiving**: if any selector in a comma-separated list is unparseable, the entire rule is discarded. This bit teams badly during the placeholder-prefix era, when people wrote lists mixing standard and vendor-specific spellings: ```css /* the unrecognised entry kills the whole rule */ ::placeholder, :-some-nonstandard-placeholder { color: #888; } ``` The fix is to write each spelling as its own rule so one bad selector cannot take the good one down with it: ```css ::placeholder { color: #888; } ``` This is a general property of selector lists, not something specific to pseudo-elements — but pseudo-elements are where people most often mix spellings, so it is where the trap springs. ## Structural rules worth knowing A few syntactic constraints follow the pseudo-element family regardless of colon count: - A pseudo-element goes at the **end** of a compound selector. `.a::before .b` selects nothing useful, and `::before:hover` is legitimate only in the sense that pseudo-*classes* may follow a pseudo-element in modern syntax; a second pseudo-element may not. - Only one pseudo-element per selector, with the narrow exception of a pseudo-element on top of another where the spec explicitly allows it. - Pseudo-elements are not allowed inside `:has()` as the subject, and support for them in some functional selectors is limited — when unsure, write the plain form and test. ## What to write today Use two colons everywhere. It is unambiguous, it is what every current specification uses, it matches what linters and formatters expect, and it means a reader never has to ask whether `:first-line` was meant as a state. If you are reading an old codebase, the single-colon spellings are not bugs and do not need a migration for correctness — but converting them removes a class of confusion for free.

  • If both forms match identically, is there any reason to migrate an old stylesheet from :before to ::before?
    Not for correctness — browsers will keep supporting the legacy spelling indefinitely for those four pseudo-elements. The reason is readability and consistency: a reader scanning `:before` next to `:hover` cannot tell the two categories apart at a glance, and mixed spellings inside one codebase invite someone to write `:placeholder` by analogy, which matches nothing.
  • What happens if you write `input::placeholder, input:-webkit-nonsense { color: #888 }`?
    The whole rule is dropped. A plain selector list is not forgiving, so an unparseable entry invalidates every selector in the list, including the valid one. Split each spelling into its own rule, or wrap the list in `:is()`/`:where()`, which are forgiving and ignore only the unknown entry.

saying these in an interview costs you the question

  • Claims single colon and double colon match different things
  • Writes :placeholder or :marker expecting them to work
  • Thinks double colon changes how strongly the rule applies
  • Believes single-colon syntax is invalid in current browsers
  • Groups valid and unknown selectors in one comma list

context