What are pseudo-classes and pseudo-elements
Pseudo-classes (:hover, :focus, :nth-child) style elements in a particular STATE or position — single colon. Pseudo-elements (::before, ::after, ::first-line) style or create a specific PART of an element — double colon. One targets state, the other targets sub-parts.
Both let you target things you can't select with plain selectors — the distinction is state/position vs. sub-part.
Pseudo-classes — target a state or position (single colon :)
Style an element based on a condition it's in — without adding a class in JS.
- User-interaction states:
:hover,:focus,:focus-visible,:active,:visited - Form/UI states:
:checked,:disabled,:required,:valid,:invalid,:placeholder-shown - Structural / positional:
:first-child,:last-child,:nth-child(2n),:only-child,:empty - Relational (modern):
:not(),:is(),:where(),:has()(the "parent selector")
button:hover { background: blue; }
input:focus-visible { outline: 2px solid; }
li:nth-child(odd) { background: #eee; }
.card:has(img) { padding-top: 0; }The element exists; the pseudo-class picks it out when it's in that state/position.
Pseudo-elements — target or create a sub-part (double colon ::)
Style a specific part of an element, or generate content that isn't in the DOM.
::before,::after— generate content (need acontentproperty); icons, decorations, clearfix.::first-line,::first-letter— typographic parts.::placeholder— input placeholder text.::selection— the highlighted/selected text.::marker— list bullets/numbers.::backdrop— behind<dialog>/fullscreen.
.tooltip::after { content: ""; /* the arrow */ }
p::first-letter { font-size: 2em; }
input::placeholder { color: gray; }
::selection { background: yellow; }::before/::after create boxes that don't exist in the DOM — they're "pseudo" elements.
The syntax distinction
- Pseudo-classes: one colon —
:hover. - Pseudo-elements: two colons —
::before(the::was introduced in CSS3 to distinguish them; browsers still accept single-colon:beforefor the old ones, but use::).
The one-line distinction
"Pseudo-classes select an element in a particular state or position — :hover, :nth-child, :checked — single colon. Pseudo-elements style or generate a part of an element — ::before, ::first-line, ::placeholder — double colon. One is which element, the other is which piece of it."
Why they matter
They keep styling in CSS instead of JS — hover/focus states, zebra-striping, generated decorations, placeholder styling — all without touching the DOM or adding event listeners.
Follow-up questions
- •Why do pseudo-elements use :: while pseudo-classes use :?
- •Why does ::before/::after require a content property?
- •What's the difference between :focus and :focus-visible?
- •What does the :has() pseudo-class enable?
Common mistakes
- •Mixing up the two concepts or the single/double colon syntax.
- •Forgetting the content property on ::before/::after (nothing renders).
- •Thinking ::before content is real DOM (it's not — not selectable/accessible by default).
- •Using :focus where :focus-visible is more appropriate.
Performance considerations
- •
Edge cases
- •::before/::after don't work on replaced elements (img, input).
- •Generated content and accessibility/screen readers.
- •Specificity of pseudo-classes vs pseudo-elements.
- •Single-colon legacy syntax still accepted for the original four pseudo-elements.
Real-world examples
- •Zebra-striped tables with :nth-child, tooltip arrows with ::after, styled placeholders with ::placeholder, :has() for parent-aware styling.