Pseudo-classes and pseudo-elements

Intermediate

Introduction

So far every selector has looked at what an element is. CSS can also see what is happening to it: whether the pointer is over it, whether a box is ticked, whether it is the last in a list. These selectors make a page respond to people without a single line of JavaScript.

How it works

State. A pseudo-class is written with one colon.

SelectorWhen it applies
a:hoverthe pointer is over it
a:activeit is being pressed
input:focusit has keyboard focus
button:focus-visibleit has focus, and the browser judges an outline to be useful
input:checkeda tick box or radio button is selected
input:disabledit is switched off
input:invalidits value breaks a validation rule

Hover does not exist on touch screens, and keyboard users never trigger it. So anything revealed on hover must have another route, and every style you give to :hover should usually be given to :focus-visible too. Using :focus-visible rather than :focus shows the outline for keyboard users without showing it on every mouse click.

For links, order matters, because these rules all have equal specificity and the later one wins: write :link, :visited, :hover, :active.

Position.

SelectorMatches
li:first-childan li that is the first child of its parent
li:last-childan li that is the last child
tr:nth-child(even)every second row
li:nth-child(3)the third child
li:nth-child(3n)every third child

Counting begins at 1, and counts all siblings, whatever type they are. p:nth-child(2) matches only if the second child happens to be a p. p:nth-of-type(2) counts only paragraphs.

Logic. :not(.done) excludes things, so li:not(:last-child) adds a divider between items without leaving one on the end. :has() selects a parent according to what it contains: form:has(input:invalid) matches a form with a bad field. For a long time this was impossible in CSS, and every major browser has supported it since late 2023.

Pseudo-elements are written with two colons. ::first-line, ::first-letter, ::placeholder and ::selection style parts of something. ::before and ::after create an extra box at the start or end inside the element:

.required::after { content: " *"; color: crimson; }

They do nothing without the content property, even if it is empty: content: "". Generated content is hard to select or copy, and screen readers handle it inconsistently. Use it for decoration, and keep real information in the HTML.

Syntax

a:hover, a:focus-visible { text-decoration: underline; }
tr:nth-child(even) { background: whitesmoke; }
.note::before { content: "Note: "; }

Example

console.log output from the page shows here.

Line by line

  1. nth-child(even) shades the second and fourth items.
  2. li:last-child::after adds a grey word to the final item only.
  3. Both hover and keyboard focus change the button's colours.

Expected result

A four-item list with Milk and Tea shaded, the word "(last)" after Tea, and a button that turns teal with the pointer over it or when reached with Tab.

In the real world

Striped tables, form error styling and focus outlines all depend on these selectors. The site you are reading uses :focus-visible so that keyboard users can always see where they are.

Common mistakes

  • Styling hover only, and leaving out keyboard users.
  • Removing focus outlines with nothing to replace them.
  • Forgetting content on ::before and ::after.
  • Confusing nth-child with nth-of-type.

Best practices

  • Give every interactive element a visible focus style.
  • Do not hide essential controls behind hover.
  • Keep important text in the HTML.
  • Use :not to avoid writing a rule and then undoing it.

Summary

Pseudo-classes select by state or position, and pseudo-elements style parts or add decoration. Hover alone is not enough: pair it with :focus-visible. :has lets a parent respond to its contents.

Quick check

1. Which should usually be styled along with :hover?
2. What is required for ::before to appear?
3. What does tr:nth-child(odd) match?

Key takeaways

  • One colon is for a state; two colons are for a part.
  • Always style focus, not only hover.
  • nth-child counts all siblings, starting from 1.
  • ::before and ::after need a content property, and should only be used for decoration.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.