What's the difference between pseudo-classes and pseudo-elements?
Take a moment.
Say your answer out loud or write it down first — retrieval is what makes it stick. Reveal when you're ready.
Short answer
Pseudo-classes style states/positions of existing elements (:hover, :focus-visible, :nth-child, :first-child) — single colon. Pseudo-elements create virtual sub-elements you can style (::before, ::after, ::placeholder, ::selection) — double colon.
Detailed explanation
Pseudo-classes select *real* elements under specific conditions — interaction state (:hover/:focus/:active/:focus-visible), structural position (:first-child, :nth-of-type(3n), :empty), input states (:checked, :disabled, :valid), and relational selectors modernized (:has() lets parents respond to children!). Pseudo-*elements* target portions of rendered content that have no DOM node: ::before/::after inject generated boxes (icons, decorations) requiring content property; ::placeholder styles input hints; ::selection colors highlights; ::first-letter drops caps. History note: CSS2 used single colons for both; CSS3 split them apart, browsers keep :before/:after working for backwards compatibility. Power combos worth citing: :focus-visible giving keyboard-only focus rings, :nth-child(odd) zebra tables, :has() enabling parent selection ('card containing checked box') — long-requested and now broadly available, changing architecture possibilities.
Example
/* Pseudo-classes: states & positions */
.btn:hover { background: indigo; }
.btn:focus-visible { outline: 2px solid indigo; }
tr:nth-child(even) { background: #f5f5f5; }
.card:has(img) { padding-top: 0; } /* parent responds to child */
/* Pseudo-elements: virtual content */
.required::after { content: " *"; color: red; }
::selection { background: indigo; color: white; }Interview tip
One-liner definitions, then flex with :has() and :focus-visible — modern selector fluency distinguishes current practitioners from textbook readers.
Common mistakes
- Muddling which takes double colons.
- Not knowing ::before/::after require the content property.