Styling states, not just elements
Every polished interface responds before any JavaScript runs. Buttons highlight under the cursor, links change once visited, and inputs glow when selected. Controls that give no feedback leave users unable to tell what is clickable, so browsers track each element's current state and CSS can select on it.
A pseudo-class is a selector suffix written with a colon, matching an element only while it is in a given state.
a:hover { color: darkgoldenrod; } /* cursor is over it */
button:active { background: gray; } /* being pressed right now */
input:focus { border-color: black; } /* selected for typing */
a:visited { color: purple; } /* already followed */| Pseudo-class | Matches while |
|---|---|
:hover | a pointer is over the element |
:active | the element is being pressed |
:focus | the element is selected for input |
:visited | the link has been followed |
In the lesson 4-3 specificity math, a:hover scores one type plus one class-level part, giving (0,1,1), because pseudo-classes count in the classes bucket.
Focus is not optional
:hover is cosmetic and :focus is accessibility. Keyboard users move through a page with the Tab key, and the focus outline, the ring the browser draws around the focused control, is the only way they can see where they are. Removing it makes the page unusable without a mouse.
The rule working teams follow is never to write outline: none unless an equally visible focus style replaces it in the same rule:
button:focus {
outline: 2px solid #d4af37; /* replaced, not removed */
outline-offset: 2px;
}Two structural pseudo-classes round out the daily set, selecting by position rather than state:
li:first-child { font-weight: bold; } /* first li in its parent */
tr:nth-child(even) { background: #eee; } /* zebra-striped table rows */| Pseudo-class | Selects by |
|---|---|
:hover, :focus, :active | state |
:first-child, :nth-child() | position |
The zebra-striping example is the most common real use of :nth-child, and it also shows why the positional group is worth knowing. It removes the need to tag alternating rows with classes in the HTML.
The state a Tab key creates
A user tabbing through a form lands on inputs matched by :focus.
:focus matches the element currently selected for input, whether it arrived there by click or by Tab. :hover requires a pointer over the element, which a keyboard user never produces.
| Input method | :hover matches | :focus matches |
|---|---|---|
| mouse pointer | yes | only after a click |
| Tab key | no | yes |
That asymmetry is why a form styled with :hover alone is invisible to navigate by keyboard. Both states are usually worth styling, and only one of them is required.
A button with real interaction states
A base style plus two state rules, one cosmetic and one required.
CSS
.cta {
background: #d4af37;
color: black;
border: none;
padding: 10px 24px;
border-radius: 20px;
font-size: 1rem;
}
.cta:hover {
background: black;
color: #d4af37;
}
.cta:focus {
outline: 3px solid black;
outline-offset: 2px;
}| Rule | Applies when | Purpose |
|---|---|---|
.cta | always | the base look |
.cta:hover | pointer over it | feedback for mouse users |
.cta:focus | focused | feedback for keyboard users |
Pseudo-classes attach to the selector with a colon and no space. outline and outline-offset are two separate declarations, with the offset pushing the ring away from the edge so it stays visible against the button's own background.
Keeping a visible focus style is an accessibility requirement rather than a decoration, since without it keyboard users cannot tell where they are.
The pointer-only state
The pseudo-class is :hover.
It matches while a pointer is over the element and stops matching the moment the pointer leaves. It is the state a mouse creates and a keyboard cannot.
| State | Created by |
|---|---|
:hover | a pointer |
:focus | a click or the Tab key |
:active | a press in progress |
Because it has no keyboard equivalent, :focus styles must exist alongside it. Touch devices complicate this further, since a tap can leave an element in a hover state until something else is tapped.