Topic 09 / 14
Pseudo-Classes & Pseudo-Elements
State Pseudo-Classes
.btn:hover { background: #4F46E5; }
.btn:active { transform: scale(0.98); } /* while pressed */
a:visited { color: purple; }
/* focus: never remove outlines — style them */
.btn:focus-visible {
outline: 2px solid #6366F1;
outline-offset: 2px;
}:focus-visible shows the ring for keyboard users but not mouse clicks — accessibility without the “ugly outline” complaint.
Structural Pseudo-Classes
li:first-child { }
li:last-child { }
li:nth-child(2) { } /* the 2nd */
li:nth-child(odd) { } /* zebra striping */
li:nth-child(3n) { } /* every 3rd */
li:not(.active) { }
p:empty { }
/* classic: borders between items, not after the last */
.menu li:not(:last-child) {
border-bottom: 1px solid #E5E7EB;
}Watch out: :nth-child counts all siblings; :nth-of-type counts only same-tag siblings.
Form State Pseudo-Classes
input:focus { border-color: #6366F1; }
input:disabled { opacity: 0.5; }
input:checked + label { font-weight: 700; } /* style label via checkbox */
input:user-invalid { border-color: #EF4444; } /* invalid AFTER interaction */
input::placeholder { color: #9CA3AF; }The :checked + sibling pattern enables pure-CSS toggles, tabs, and accordions.
Pseudo-Elements — ::before & ::after
Two free child boxes on any element, requiring content:
.required::after {
content: " *";
color: #EF4444;
}
/* decorative shapes — most common use */
.section-title::before {
content: ""; /* empty but rendered */
display: inline-block;
width: 2rem; height: 3px;
background: #6366F1;
margin-right: .75rem;
vertical-align: middle;
}
/* expand a card's clickable area to the whole card */
.card { position: relative; }
.card a::after {
content: "";
position: absolute;
inset: 0; /* covers the entire card */
}Also: ::selection (highlight colour), ::marker (list bullets), ::first-letter (drop caps).
Modern Selectors: :is(), :where(), :has()
/* :is — collapse repetitive lists */
:is(h1, h2, h3) a { color: inherit; }
/* :where — same, but ZERO specificity (easy to override later) */
:where(ul, ol) { padding-left: 1.2rem; }
/* :has — the "parent selector", finally */
.card:has(img) { padding: 0; } /* cards that contain an image */
.field:has(input:invalid) label { color: #EF4444; } /* style label by input state */
form:has(:checked) .submit { opacity: 1; }:has() removes a whole category of JavaScript — styling parents based on children is now pure CSS in every modern browser.