•
type, class, id and *, and what each one costs in specificity
•
[attr] tests presence, [attr="v"] tests an exact value
•
^= prefix, $= suffix, *= substring, ~= whitespace-word, |= prefix-or-hyphen
•
a trailing i makes the attribute value match case-insensitively
•
a space matches any depth, > only a direct child
•
+ matches the immediately next sibling, ~ any later sibling
•
selectors match forward and downward only, which is why :has() was needed
•
:hover, :active, :focus, and :focus-visible vs :focus-within
•
an+b counts from 1, so 2n is even, 2n+1 odd, -n+3 the first three
•
:nth-child counts every sibling, :nth-of-type only same-tag siblings
•
:first-child, :last-child, :only-child, and :empty (text and whitespace count as content)
•
:is() groups, :where() groups at zero specificity, :not() excludes
•
one invalid selector voids a plain A, B list but not :is(A, B)
•
:has() styles an element from its descendants or later siblings (baseline 2023)
•
::before/::after need a content value and render inside the element's box
•
::first-line, ::first-letter, ::selection, ::placeholder, ::marker
•
:root, :target, :lang(), :dir()
•
:checked, :disabled, :required, :valid/:invalid, :in-range
•
they have no DOM node for JS to select, and replaced elements have none at all