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.
Link Pseudo-classes
Link pseudo-classes style anchor (<a>) elements based on their interaction
state. These four states must be defined in the LVHA order (Link, Visited, Hover, Active)
to work correctly due to CSS specificity rules.
<a href="#unvisited">Unvisited link</a>
<a href="#">Hover over me</a>
<a href="#" onclick="return false;">Click and hold</a>
Rendered Result:
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)
<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.
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.
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%;
}
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.
<!-- 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.
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.
: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