Back to CSS
CSS
easy
mid

What are pseudo classes and pseudo elements in CSS?

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.

4 min read·~5 min to think through

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")
css
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 a content property); 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.
css
.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 :before for 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.

Senior engineer discussion

Seniors give the crisp distinction — state/position vs sub-part, single vs double colon — and show range with modern selectors (:is/:where/:has, :focus-visible). They note the content-property requirement and that generated content isn't real, accessible DOM.

Related questions