Interactive states and structural styling are impossible without them, and they replace lots of extra markup.
Pseudo-classes (one colon) select elements in a particular state or position.
- User action:
:hover,:active,:focus, and:focus-visible, which matches only when the browser decides a focus ring should be shown (keyboard focus, not mouse clicks). Use it for focus styles. - Form state:
:checked,:disabled,:required,:invalid,:user-invalid,:placeholder-shown. - Structure:
:first-child,:last-child,:only-child,:nth-child(2n+1)(odd items),:nth-child(3),:nth-of-type(),:empty. - Negation:
:not(.active). - Links:
:linkand:visited(for privacy, only colours can change on visited links).
Pseudo-elements (two colons) style a part of an element or create a box that isn't in the HTML.
::beforeand::afterinsert a generated child at the start or end. They require thecontentproperty, even if it's justcontent: "", and they don't exist on void elements likeimgandinput.::first-line,::first-letter,::placeholder,::selection,::marker(list bullets),::backdrop(behind a modal dialog).
Generated content is decoration. Screen readers may or may not read it, and users can't select it, so never put essential information in content.
A useful recipe: li:not(:last-child) adds separators between items without a trailing one.
Example
a:hover { text-decoration-thickness: 2px; }
button:focus-visible { outline: 3px solid royalblue; } /* keyboard focus only */
tr:nth-child(even) { background: #f5f5f5; } /* zebra rows */
li:not(:last-child) { border-bottom: 1px solid #eee; }
input:user-invalid { border-color: crimson; } /* only after the user interacts */
.required::after { content: " *"; color: crimson; }
blockquote::before { /* decorative quote mark */
content: "\201C";
font-size: 3rem;
line-height: 0;
}
li::marker { color: teal; }
::selection { background: #fde68a; }Remember
- Pseudo-classes = state/position (:hover); pseudo-elements = parts (::before)
- Use :focus-visible for focus rings
- ::before/::after need a content property
- Generated content is decoration, not information
Watch out for
- Forgetting content: "" so ::before never appears.