This is a searchable reference for CSS pseudo-classes — the single-colon selectors that match elements based on their state, position in the document tree, or relationship to other elements. It covers the structural, input-state, linguistic, location and functional pseudo-classes you reach for when styling links, form controls, list items and interactive widgets.
How it works
A pseudo-class is written as a colon followed by a keyword, optionally with arguments in parentheses. Unlike a class attribute, it is not present in your markup — the browser evaluates it dynamically. State pseudo-classes such as :hover, :focus and :checked react to user interaction or form state. Tree-structural ones such as :first-child and :nth-child() match by position among siblings. The :nth-child() family uses an An+B micro-syntax: A is the cycle size, n counts from zero, and B is the offset, so :nth-child(3n+1) matches the 1st, 4th, 7th child and so on. Functional pseudo-classes — :is(), :where(), :not() and :has() — take selector lists and combine logic; :has() is the long-awaited relational (“parent”) selector.
The pseudo-classes worth knowing deeply
:focus-visible vs :focus: :focus fires on every focused element including those clicked with a mouse. :focus-visible fires only when the browser determines that a visible focus indicator is appropriate — typically keyboard navigation. Using :focus-visible for ring styles and :focus only for other state-dependent styles is the modern accessibility-correct pattern.
:has() — the parent selector: article:has(> img) matches articles that have a direct-child image. form:has(:invalid) matches any form containing an invalid input. This is the closest CSS has come to a parent selector, and it opened up patterns previously requiring JavaScript.
:is() and :where(): Both match if any selector in the list applies.
The difference is specificity. :is(h1, h2, h3) { ... } carries the weight of h1 (the most specific argument). :where(h1, h2, h3) { ... } carries zero specificity — ideal for resets you want any class to override without specificity battles.
:nth-child() patterns:
| Expression | Matches |
|---|---|
:nth-child(odd) or (2n+1) | 1st, 3rd, 5th … |
:nth-child(even) or (2n) | 2nd, 4th, 6th … |
:nth-child(3n) | 3rd, 6th, 9th … |
:nth-child(3n+1) | 1st, 4th, 7th … |
:nth-child(-n+3) | First three items |
:nth-last-child(1) | Last item |
:not() with multiple arguments: Modern CSS allows :not(a, button, [disabled]) in a single :not(), which was not possible in earlier CSS 3. Each argument is evaluated independently and the selector matches if none of them match the element.
Specificity and tips
Most pseudo-classes add the weight of a class (the middle digit of specificity). The exceptions matter: :where() always adds zero, and :is() / :not() / :has() inherit the specificity of their most specific argument. Use :focus-visible instead of :focus so you only show focus rings for keyboard users, and reach for :focus-within to style a container when any child is focused. Note that :empty treats whitespace as content, so a node containing only spaces will not match.
Example
/* Zebra-stripe a table */
tr:nth-child(even) { background: #f5f5f5; }
/* Style a card that contains an image */
.card:has(img) { padding-top: 0; }
/* Low-specificity reset that is easy to override */
:where(ul, ol) { margin: 0; padding: 0; }
/* Show error state on a form field's container */
.field:has(input:invalid) { color: red; }
Everything runs in your browser; nothing you search is uploaded.