What a pseudo-class is
A pseudo-class is a keyword after a colon that selects elements by their position in the document or their state. You attach it to a normal selector: li:first-child, input:focus, a:hover.
State pseudo-classes for links and the mouse (:hover, :active, :visited) have their own page, CSS hover.
:nth-child() and the an+b formula
:nth-child() takes a number, a keyword, or a formula an+b. The browser tries n = 0, 1, 2, ... and matches every position the formula produces (positions start at 1).
| Formula | Matches positions | Meaning |
|---|---|---|
3 | 3 | The third child |
odd or 2n+1 | 1, 3, 5, ... | Odd rows |
even or 2n | 2, 4, 6, ... | Even rows |
3n | 3, 6, 9, ... | Every third |
n+4 | 4, 5, 6, ... | The fourth and after |
-n+3 | 1, 2, 3 | The first three |
3n+1 | 1, 4, 7, ... | Every third, starting at the first |
Type a formula and watch which items match:
Try odd, even, 4, n+7, -n+5 and 2n+3. An invalid formula matches nothing.
The last-counting twin is :nth-last-child(): li:nth-last-child(-n+2) selects the last two items.
nth-child vs nth-of-type
:nth-child() counts every sibling element, whatever its tag, and only then checks the rest of the selector. :nth-of-type() counts only siblings of the same tag. The difference shows as soon as the siblings are mixed:
The same pairs exist for the ends: :first-child / :first-of-type, :last-child / :last-of-type, :only-child / :only-of-type.
:not(), :is() and :where()
| Pseudo-class | Example | Matches |
|---|---|---|
:not(X) | li:not(.done) | Elements that do not match X |
:is(X, Y) | :is(h1, h2, h3) a | Elements matching any of the list |
:where(X, Y) | :where(ul, ol) li | Same as :is(), with zero specificity |
:has(X) | li:has(> input:checked) | Elements containing a match for X |
:not() is the one used every day, for example to put a separator between items but not after the last one:
State pseudo-classes: focus, checked, disabled, invalid
| Pseudo-class | Matches |
|---|---|
:focus | The element that has keyboard or mouse focus |
:focus-visible | Focused, and the browser decides a ring should show (usually keyboard focus) |
:focus-within | An element that contains the focused element |
:checked | A checked checkbox, radio or selected <option> |
:disabled / :enabled | Form controls that are (not) disabled |
:required / :optional | Inputs with or without required |
:invalid / :valid | Inputs whose value fails or passes validation |
:user-invalid | Invalid, but only after the user has interacted with it |
:placeholder-shown | An input currently showing its placeholder |
:empty | An element with no children, not even text |
:target | The element whose id matches the URL's #fragment |
Click into the email field: its label turns blue through :focus-within. Type something that is not an email and leave the field: :user-invalid turns the border amber. Plain :invalid would have marked the empty required field amber before you touched it.
Whatever you do with :focus, keep a visible focus style. Removing the outline without a replacement leaves keyboard users unable to see where they are.
:has(), the parent selector
:has() matches an element by what it contains. It is the long-requested "parent selector", and it works in current Chrome, Firefox and Safari.
Pick the other plan: the whole card follows the radio button, with no JavaScript.
Common mistakes
- A space before the colon.
li :first-childmeans "a first child inside an li". Writeli:first-child. - Expecting
p:nth-child(1)to mean "the first p". It means "the first child, if it is a p". Use:first-of-typefor the first p. - Counting from 0. Positions start at 1:
:nth-child(0)matches nothing. - Removing focus outlines.
:focus { outline: none }with no replacement breaks keyboard navigation. - Confusing
:and::.:beforeis an old spelling of the pseudo-element::before; pseudo-classes always use one colon.
Frequently Asked Questions
What is a pseudo-class in CSS?
A keyword after a colon that selects elements by state or position rather than by name, such as a:hover, li:first-child or input:focus. It is added to a normal selector, and most pseudo-classes count like a class for specificity (:where() counts zero).
How does nth-child work in CSS?
:nth-child(an+b) matches elements whose position among their siblings is a×n+b for n = 0, 1, 2 and so on, counting from 1. :nth-child(3) is the third child, :nth-child(odd) or 2n+1 every odd one, and :nth-child(n+4) the fourth and after.
What is the difference between nth-child and nth-of-type?
:nth-child() counts all sibling elements, then checks the rest of the selector; :nth-of-type() counts only siblings of the same element type. In a list of an h2 followed by paragraphs, p:nth-child(1) matches nothing while p:nth-of-type(1) matches the first paragraph.
How do you use :not() in CSS?
Put the selector to exclude inside the parentheses: li:not(.done) matches every list item without the class done. It accepts a list too, input:not([type="checkbox"], [type="radio"]).
What is the difference between a pseudo-class and a pseudo-element?
A pseudo-class (one colon, :hover) selects a whole existing element in some state. A pseudo-element (two colons, ::before) targets a part of an element or creates generated content.