Pseudo-classes

What You'll Learn

  • What pseudo-classes are and how they differ from regular selectors
  • How to style elements based on user interaction (:hover, :focus, :active)
  • Structural pseudo-classes for targeting elements by position
  • Form validation pseudo-classes for better UX
  • Advanced pseudo-classes like :not(), :is(), :where(), and :has()

Introduction to Pseudo-classes

Pseudo-classes are special keywords that select elements based on their state or position in the document tree. They use a single colon (:) syntax and allow you to style elements dynamically without adding extra classes or JavaScript.

Unlike regular selectors that match elements based on attributes or structure, pseudo-classes respond to user interactions, validate form states, and target elements by their position among siblings. This makes them incredibly powerful for creating interactive, accessible interfaces with pure CSS.

User Interaction Pseudo-classes

These pseudo-classes respond to user interactions like focus, hover, and form input states. They're essential for creating accessible, user-friendly interfaces with visual feedback.

Common User Interaction States

  • :hover - Element is being hovered over with a mouse
  • :focus - Element has keyboard focus (critical for accessibility)
  • :active - Element is being activated (e.g., button being clicked)
  • :disabled - Form element is disabled
  • :checked - Checkbox or radio button is checked

Syntax

input:focus { border: 2px solid #0066cc; background-color: #e7f3ff; box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1); } input:disabled { background-color: #e9ecef; cursor: not-allowed; opacity: 0.6; } input[type="checkbox"]:checked + label { font-weight: bold; color: #28a745; }

HTML Example

<input type="text" placeholder="Click to focus"> <input type="text" disabled placeholder="Cannot type here"> <input type="checkbox" id="cb1"> <label for="cb1">Check me</label>

Rendered Result:

Structural Pseudo-classes: First, Last, and Nth

Structural pseudo-classes select elements based on their position among siblings. They're perfect for styling lists, tables, and other repeating elements without adding extra classes.

Basic Position Selectors

li:first-child { color: #28a745; font-weight: bold; } li:last-child { color: #dc3545; font-weight: bold; } li:nth-child(2) { background-color: #fff3cd; }

HTML Example

<ul> <li>First child (green, bold)</li> <li>Second child (yellow background)</li> <li>Third child (normal)</li> <li>Fourth child (normal)</li> <li>Last child (red, bold)</li> </ul>

Rendered Result:

Related Selectors

  • :first-child - First child of its parent
  • :last-child - Last child of its parent
  • :nth-child(n) - Nth child (1-indexed)
  • :first-of-type - First sibling of its type
  • :last-of-type - Last sibling of its type
  • :nth-of-type(n) - Nth sibling of its type

Nth-child Formulas and Patterns

The :nth-child() pseudo-class accepts powerful formula syntax that lets you select repeating patterns of elements. The formula is an+b where 'a' is the cycle size and 'b' is the offset.

Common Patterns

/* Zebra striping: alternating rows */ li:nth-child(odd) { background-color: #f8f9fa; } li:nth-child(even) { background-color: #e9ecef; } /* Every 3rd item */ li:nth-child(3n) { color: #0066cc; font-weight: bold; }

HTML Example

<ul> <li>Item 1 (odd)</li> <li>Item 2 (even)</li> <li>Item 3 (odd + every 3rd)</li> <li>Item 4 (even)</li> <li>Item 5 (odd)</li> <li>Item 6 (even + every 3rd)</li> </ul>

Rendered Result:

Formula Examples

  • 2n or even - Every 2nd element (2, 4, 6, 8...)
  • 2n+1 or odd - Every 2nd element starting at 1 (1, 3, 5, 7...)
  • 3n - Every 3rd element (3, 6, 9, 12...)
  • 3n+1 - Every 3rd element starting at 1 (1, 4, 7, 10...)
  • 4n+2 - Every 4th element starting at 2 (2, 6, 10, 14...)
  • -n+3 - First 3 elements (3, 2, 1)
  • n+4 - All from 4th onward (4, 5, 6, 7...)

:not() - Negation Pseudo-class

The :not() pseudo-class selects elements that do NOT match the selector inside the parentheses. It's extremely useful for applying styles to everything except specific elements.

Syntax

/* Style all buttons except the primary one */ button:not(.primary) { background-color: #6c757d; color: white; } /* All list items except the first */ li:not(:first-child) { margin-top: 0.5rem; } /* All inputs except checkboxes and radios */ input:not([type="checkbox"]):not([type="radio"]) { width: 100%; }

HTML Example

<button class="primary">Primary Button</button> <button>Regular Button</button> <button>Regular Button</button>

Rendered Result:

:only-child - Single Child Selector

The :only-child pseudo-class matches elements that are the only child of their parent. It's useful for applying special styling when an element appears alone.

Syntax

.container p:only-child { border: 2px dashed #ff6600; padding: 1rem; background: #fff5f0; }

HTML Example

<!-- Matches: only one paragraph --> <div class="container"> <p>I'm the only child!</p> </div> <!-- Doesn't match: multiple paragraphs --> <div class="container"> <p>I have a sibling</p> <p>I have a sibling</p> </div>

Rendered Result:

Related Selectors

  • :only-child - Only child of any type
  • :only-of-type - Only sibling of its element type

Form Validation Pseudo-classes

Form validation pseudo-classes automatically style form elements based on their validation state. They provide instant visual feedback to users without any JavaScript, making forms more accessible and user-friendly.

Validation States

/* Valid and invalid states */ input:valid { border-color: #28a745; background-color: #f0fff4; } input:invalid { border-color: #dc3545; background-color: #fff5f5; } /* Required and optional fields */ input:required { border-left: 3px solid #ffc107; } input:optional { border-left: 3px solid #6c757d; } /* Range validation */ input:in-range { border-color: #28a745; } input:out-of-range { border-color: #dc3545; }

HTML Example

<input type="email" required placeholder="user@example.com"> <input type="url" placeholder="https://example.com"> <input type="number" min="18" max="100" placeholder="Age">

Rendered Result:

All Form Validation Pseudo-classes

  • :valid - Input passes validation
  • :invalid - Input fails validation
  • :required - Input has required attribute
  • :optional - Input is not required
  • :in-range - Number is within min/max range
  • :out-of-range - Number is outside min/max range
  • :placeholder-shown - Input is showing placeholder
  • :blank - Input is empty (newer spec)

Modern Pseudo-classes: :is(), :where(), and :has()

Modern CSS includes powerful new pseudo-classes that simplify complex selectors and enable parent selection. These are game-changers for writing maintainable CSS.

:is() - Matches-Any Pseudo-class

:is() takes a list of selectors and matches if any of them match. It simplifies long selector lists and has the specificity of its most specific argument.

/* Before: repetitive selectors */ h1 a:hover, h2 a:hover, h3 a:hover { color: red; } /* After: using :is() */ :is(h1, h2, h3) a:hover { color: red; } /* Complex example */ :is(article, section, aside) :is(h1, h2, h3) { font-family: Georgia, serif; }

:where() - Zero-Specificity :is()

:where() works exactly like :is(), but has zero specificity. This makes it perfect for default styles that you want to easily override.

/* These have zero specificity */ :where(h1, h2, h3) { margin-top: 0; } /* Easy to override with a simple class */ .special { margin-top: 2rem; /* This wins! */ }

:has() - Parent Selector

:has() is revolutionary - it selects an element if it contains elements matching the selector inside. This enables true parent selection and conditional styling based on descendants.

/* Card with an image gets different layout */ .card:has(img) { display: grid; grid-template-columns: 200px 1fr; } /* Form with invalid input gets red border */ form:has(input:invalid) { border: 2px solid #dc3545; } /* List item that contains a link */ li:has(> a) { padding-left: 0; } /* Section without headings */ section:not(:has(h2, h3)) { margin-top: 0; }

Additional Pseudo-classes Reference

There are many more pseudo-classes available in CSS. Here's a quick reference of additional ones you might encounter:

Content State

  • :empty - Element has no children (including text nodes)
  • :blank - Element is empty or contains only whitespace
  • :target - Element matching the URL fragment (#id)

Language and Direction

  • :lang() - Element in a specific language
  • :dir() - Element with specific text direction (ltr/rtl)

Input States

  • :enabled - Form element is enabled
  • :read-only - Input is read-only
  • :read-write - Input is editable
  • :indeterminate - Checkbox in indeterminate state
  • :default - Default form element in a group

Fullscreen and Media

  • :fullscreen - Element in fullscreen mode
  • :picture-in-picture - Video in picture-in-picture mode
  • :playing - Media is playing
  • :paused - Media is paused

Key Takeaways