22. CSS Pseudo-classes
What you will learn
- What a pseudo-class is
- Interaction states: hover, focus, active
- Position selectors: first-child, last-child, nth-child
- Form states: checked, disabled, required, invalid
- Useful helpers:
:not()and:is()
What is a pseudo-class?
A pseudo-class selects an element in a particular state or position. It is written with a single colon after a selector, for example button:hover.
Syntax
selector:pseudo-class {
property: value;
}
Interaction states
| Pseudo-class | Matches when |
|---|---|
:hover |
The pointer is over the element |
:focus |
The element has keyboard focus (or is clicked into) |
:focus-visible |
Focus should be shown, typically for keyboard users |
:active |
The element is being pressed |
Example
<style>
button {
padding: 10px 20px;
color: white;
background-color: #0d6efd;
border: 0;
border-radius: 6px;
cursor: pointer;
}
button:hover { background-color: #082b59; }
button:active { transform: scale(0.97); }
button:focus-visible { outline: 3px solid #f59f00; outline-offset: 3px; }
input:focus { background-color: #fffbe6; }
</style>
<button type="button">Hover, press, or Tab to me</button>
<p><input type="text" placeholder="Click into me"></p>
Output (the state changes work in the live editor)
Position in a family
| Pseudo-class | Matches |
|---|---|
:first-child |
An element that is the first child of its parent |
:last-child |
An element that is the last child |
:nth-child(2) |
The second child |
:nth-child(odd) / (even) |
Alternating children |
:nth-child(3n) |
Every third child |
Example
<style>
li:first-child { font-weight: bold; color: #0d6efd; }
li:last-child { color: crimson; }
li:nth-child(even) { background-color: #eaf5ff; }
</style>
<ul>
<li>One (first)</li>
<li>Two</li>
<li>Three</li>
<li>Four (last)</li>
</ul>
Output
- One (first)
- Two
- Three
- Four (last)
Form states
| Pseudo-class | Matches |
|---|---|
:checked |
A ticked checkbox or selected radio button |
:disabled / :enabled |
Fields that can or cannot be used |
:required / :optional |
Fields with or without required |
:invalid / :valid |
Fields whose content passes or fails validation |
:placeholder-shown |
A field still showing its placeholder |
Example
<style>
input:required { border: 2px solid #0d6efd; }
input:disabled { background-color: #e9ecef; }
input:user-invalid { border-color: crimson; }
input:checked + label { font-weight: bold; color: #146c43; }
</style>
<p><input type="email" required placeholder="Required email"></p>
<p><input type="text" disabled value="Disabled"></p>
<p><input type="checkbox" id="ok"> <label for="ok">Tick me</label></p>
:invalid matches an empty required field straight away, which can look rude before anyone has typed. :user-invalid waits until the visitor has interacted with the field, so it is friendlier. It is supported by current major browsers, but check older ones if you need to support them.
Negation and grouping
:not(selector)matches everything except the selector.:is(a, b, c)matches any of the listed selectors, which shortens long lists.
Example
<style>
li:not(:last-child) { border-bottom: 1px solid #cfe6ff; }
:is(h1, h2, h3) { color: #082b59; }
</style>
<h3>Shopping list</h3>
<ul>
<li>Bread</li>
<li>Milk</li>
<li>Eggs</li>
</ul>
Output
Shopping list
- Bread
- Milk
- Eggs
Common mistakes
- Using two colons (
::hover). Pseudo-classes use one; pseudo-elements use two. - Adding a space before the colon:
a :hovermeans something different froma:hover. - Relying on hover alone. Touch screens have no hover, so important information must be visible without it.
- Removing focus styles and leaving keyboard users lost.
Exercise
Make every list item show a #eaf5ff background on hover, and make the first item bold.
Show solution
<style>
li:hover { background-color: #eaf5ff; }
li:first-child { font-weight: bold; }
</style>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
Knowledge check
1. How many colons does a pseudo-class use?
One.
2. Which pseudo-class selects every other row?
:nth-child(even) or :nth-child(odd).
3. Which pseudo-class matches everything except a given selector?
:not()
Summary
- Pseudo-classes target states and positions.
- Use
:hover,:focus-visible,:nth-child(), and form-state selectors. :not()and:is()make selectors shorter and clearer.