CSS Pseudo-Classes

A pseudo-class selects an element based on its state or position — things that are not written in the HTML itself. You cannot look at a link and tell whether a user is hovering it; that state changes moment to moment. Pseudo-classes let CSS respond to those live conditions.

All pseudo-classes use a colon prefix: :hover, :focus, :first-child.

User Interaction Pseudo-Classes

These respond to what the user does with a mouse, keyboard, or touch.

:hover

Applies when the user's mouse pointer is over an element.

button:hover {
  background-color: darkblue;
  color: white;
}
DIAGRAM — :hover state:

Normal state:    [ Click Me ]  ← gray background
On hover:        [ Click Me ]  ← blue background (mouse is over it)
After hover:     [ Click Me ]  ← gray again

:focus

Applies when an element receives keyboard focus — usually after tabbing to it or clicking an input field.

input:focus {
  border: 2px solid blue;
  outline: none;
}

Important: Never remove :focus styles without providing an alternative. Keyboard users need visible focus indicators to know where they are on the page.

:active

Applies at the moment a user clicks or taps an element — just the instant of the press, not before or after.

button:active {
  transform: scale(0.98);
  background-color: darkred;
}

:visited and :link

These two work on anchor tags.

  • :link — a link the user has not visited yet
  • :visited — a link the user has visited before
a:link    { color: blue; }
a:visited { color: purple; }

Form State Pseudo-Classes

These target form elements based on their current state.

:checked

Applies to checkboxes and radio buttons that are currently checked.

input:checked + label {
  font-weight: bold;
  color: green;
}

:disabled and :enabled

input:disabled {
  background-color: #eee;
  cursor: not-allowed;
}

input:enabled {
  background-color: white;
}

:required and :optional

input:required {
  border-left: 4px solid red;
}

input:optional {
  border-left: 4px solid gray;
}

:valid and :invalid

These apply based on whether the input value passes its built-in validation (like type="email" or required).

input:valid   { border-color: green; }
input:invalid { border-color: red; }

Structural Pseudo-Classes

These select elements based on their position inside a parent.

:first-child and :last-child

DIAGRAM — first-child and last-child:

<ul>
  <li>Item 1</li>   ← :first-child
  <li>Item 2</li>
  <li>Item 3</li>   ← :last-child
</ul>
li:first-child { font-weight: bold; }
li:last-child  { color: gray; }

:nth-child()

Selects elements by their position number inside a parent. You can use a number, a keyword, or a formula.

/* Every second row */
tr:nth-child(even) {
  background-color: #f2f2f2;
}

/* Every odd row */
tr:nth-child(odd) {
  background-color: white;
}

/* Every 3rd element */
li:nth-child(3n) {
  color: red;
}

/* 3rd element exactly */
p:nth-child(3) {
  font-size: 1.2rem;
}
DIAGRAM — nth-child(2n) on a list:

Item 1   ← 1 → not selected
Item 2   ← 2 → ✅ selected (2n: n=1)
Item 3   ← 3 → not selected
Item 4   ← 4 → ✅ selected (2n: n=2)
Item 5   ← 5 → not selected
Item 6   ← 6 → ✅ selected (2n: n=3)

:nth-last-child()

Same as :nth-child() but counts from the end.

li:nth-last-child(1) { /* same as :last-child */ }
li:nth-last-child(2) { /* second from the end */ }

:only-child

Selects an element that is the only child of its parent.

p:only-child {
  font-size: 1.3rem;
}

:first-of-type and :last-of-type

These work like :first-child but only count elements of the same type.

DIAGRAM — first-child vs first-of-type:

<div>
  <h2>Heading</h2>   ← :first-child AND h2:first-of-type
  <p>Para 1</p>      ← p:first-of-type (first p in parent)
  <p>Para 2</p>
</div>

p:first-child      → nothing! The first child is an h2, not a p.
p:first-of-type    → selects "Para 1" ✅

:nth-of-type()

/* Style every 2nd image */
img:nth-of-type(2n) {
  float: right;
}

:not() — The Negation Pseudo-Class

:not() selects everything that does not match the selector inside it.

/* All buttons except the one with class .cancel */
button:not(.cancel) {
  background-color: blue;
  color: white;
}

/* All inputs except submit buttons */
input:not([type="submit"]) {
  border: 1px solid gray;
}

:is() and :where() — Grouping Made Easy

:is() lets you write one rule that matches multiple selectors at once. It reduces repetition.

/* Without :is() — repetitive */
h1, h2, h3, h4 {
  font-family: Georgia, serif;
}

/* With :is() — cleaner */
:is(h1, h2, h3, h4) {
  font-family: Georgia, serif;
}

:where() works exactly like :is() but has zero specificity. This makes it easy to override with any other rule.

:has() — The Parent Selector

:has() selects a parent based on what it contains. This was impossible in CSS for a long time and is now supported in all modern browsers.

/* Style a card that contains an image */
.card:has(img) {
  border: 2px solid blue;
}

/* Style a section that has no h2 */
section:not(:has(h2)) {
  background-color: lightyellow;
}

:root

:root targets the root element of the page, which is the <html> element. It is commonly used to declare CSS custom properties (variables).

:root {
  --primary-color: #3498db;
  --font-size-base: 16px;
}

Common Pseudo-Class Quick Reference

Pseudo-ClassWhat It Targets
:hoverElement under the mouse
:focusElement with keyboard focus
:activeElement being clicked
:checkedChecked checkbox or radio
:disabledDisabled form field
:first-childFirst child element
:last-childLast child element
:nth-child(n)Element at position n
:not(x)Anything that is not x
:is(a,b,c)Any element that matches a, b, or c
:has(x)Element that contains x
:rootThe html element

Pseudo-classes let you style interactive states and structural positions without adding extra classes to your HTML. They keep your markup clean while giving you precise control over how elements look at every moment.

Leave a Comment

Your email address will not be published. Required fields are marked *