Pseudo-classes and pseudo-elements

CSS · Practical · 12 min

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: :link and :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.

  • ::before and ::after insert a generated child at the start or end. They require the content property, even if it's just content: "", and they don't exist on void elements like img and input.
  • ::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.

Check yourself

  1. Which selector matches the 1st, 3rd, 5th... items of a list?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Pseudo-classes