Skip to main content
CodeOath
← All posts

HTML & CSS82 min total · 18 parts

CSS Fundamentals: The Box Model, Specificity, Positioning, and Layout

Part 9 of 18 · ~1 min

Pseudo-classes and Pseudo-elements

A pseudo-class — single colon, : — doesn't care what an element is; it matches based on the state that element is currently in, or where it happens to sit among its siblings:

.card__add:hover        { background: var(--accent-color-dark); }
.card__add:focus-visible { outline: 2px solid var(--accent-color); outline-offset: 2px; }
.cart-drawer li:nth-child(even) { background: var(--stripe); }
.card__price:not(.is-sale)      { color: var(--text-color); } /* every price that ISN'T a sale price */

The formula inside :nth-child() is an+b: plug in 2n and you get every second element starting from the 2nd — which is what's striping the drawer's rows above — 2n+1 (or, equivalently, the keyword odd) catches everything 2n skipped, and dropping in a bare integer like 3 matches nothing but that single position, counted from the first child.

A pseudo-element — double colon, ::, though the earliest four also still answer to a single colon for backward compatibility — doesn't target an element at all. It targets a fragment of one: something with no tag of its own anywhere in the markup. The card needs a small warning icon in front of its "Only 2 left" stock note, and manufacturing content out of nothing is exactly what ::before is for:

.card__stock-warning::before {
  content: "⚠";
  margin-right: 4px;
}

Neither ::before nor ::after renders anything until content is explicitly set — an empty string satisfies it — and what they do produce sits almost entirely outside the accessibility tree: it can't be selected as text, and most screen readers pass over it in silence. That warning icon is decoration layered on top of the real warning text ("Only 2 left"), never a stand-in for it — anything someone genuinely needs to read belongs in actual markup, never tucked inside a content: value.