Comprehensive Guide to CSS Pseudo-Classes and Their Usage

Whether you’re new to CSS or have years of experience, you’ve likely encountered pseudo-classes. The most commonly recognized pseudo-class is probably :hover, which allows us to style an element when it’s in the hover state, such as when a mouse pointer hovers over it.

Building on concepts from our previous discussions on margin:auto and CSS Floats, this post provides a detailed examination of pseudo-classes. We’ll explore what pseudo-classes are, how they function, how they can be categorized, and how they differ from pseudo-elements.

Understanding Pseudo-Classes

A pseudo-class is a keyword added to CSS selectors to define a specific state of an HTML element. You can add a pseudo-class to a CSS selector using the colon syntax :, like this: a:hover { ... }.

A CSS class is an attribute applied to HTML elements to enforce the same style rules, such as for top menu items or sidebar widget titles. Essentially, CSS classes group or classify HTML elements that share common traits.

Pseudo-classes are similar to CSS classes because they also apply style rules to elements with shared characteristics. However, while standard classes are user-defined and visible in the source code (e.g., <div class="myClass">), pseudo-classes are applied by user agents (e.g., web browsers) based on the current state of the HTML element.

The Role of Pseudo-Classes

The purpose of regular CSS classes is to classify or group elements. Developers group elements based on intended styling, creating classes like “menu-items,” “buttons,” or “thumbnails” to ensure consistent design across similar elements. These groupings are based on characteristics defined by the developers.

For example, a developer might use a <div> as a thumbnail and classify it with a “thumbnail” class.

 <div class="thumbnail">[...]</div>

However, HTML elements possess inherent characteristics based on their state, position, nature, and interaction with the page and user. These traits are not typically marked in HTML code, but can be targeted with pseudo-classes in CSS. Examples include:

  • An element that is the last child within its parent element
  • A link that has been visited
  • An element that has gone fullscreen

These are the types of characteristics typically addressed by pseudo-classes. To better understand the difference between classes and pseudo-classes, let’s consider using the class .last to identify the last elements in various parent containers.

 <ul>
 <li>item 1</li>
 <li>item 2</li>
 <li>item 3</li>
 <li class="last">item 4</li>
 </ul>
 
 <select>
 <option>option 1</option>
 <option>option 2</option>
 <option>option 3</option>
 <option class="last">option 4</option>
 </select>

You can style these last-child elements with the following CSS:

 li.last {
   text-transform: uppercase;
 }
 
 option.last {
   font-style: italic;
 }

But what happens when the last element changes? You’ll need to manually update the .last class from the previous last element to the new one.

This hassle of updating classes can be avoided for certain common characteristics. For example, using a predefined :last-child pseudo-class is highly beneficial. With this, there’s no need to mark the last element in the HTML code, and you can still style it with the following CSS:

 li:last-child {
   text-transform: uppercase;
 }
 
 option:last-child {
   font-style: italic;
 }

Main Types of Pseudo-Classes

CSS offers a variety of pseudo-classes that allow developers to target elements based on specific characteristics that might otherwise be difficult to access. You can find a comprehensive list of standard pseudo-classes on MDN.

1. Dynamic Pseudo-Classes

Dynamic pseudo-classes are applied to HTML elements dynamically, based on the state they transition into due to user interactions. Some examples include :hover, :focus, :link, and :visited, all of which are commonly used with the <a> anchor tag.

 a:visited {
   color: #8D20AE;
 }
 
 .button:hover,
 .button:focus {
   font-weight: bold;
 }
2. State-Based Pseudo-Classes

State-based pseudo-classes are applied to elements when they are in a specific static state. Common examples include:

  • :checked for checkboxes (<input type="checkbox">)
  • :fullscreen to target any element currently displayed in full-screen mode
  • :disabled for elements that can be disabled, such as <input>, <select>, and <button>.

The :checked pseudo-class is particularly popular, as it indicates whether a checkbox is selected.

 .checkbox:checked + label {
   font-style: italic;
 }
 
 input:disabled {
   background-color: #EEEEEE;
 }
3. Structural Pseudo-Classes

Structural pseudo-classes target elements based on their position within the document structure. Some of the most commonly used examples include :first-child, :last-child, and :nth-child(n). These can be used to style specific child elements based on their position within a container. Another example is :root, which targets the highest-level parent element in the DOM.

4. Miscellaneous Pseudo-Classes

There are also pseudo-classes that don’t fit neatly into other categories, such as:

  • :not(x), which selects elements that don’t match the specified selector x
  • :lang(language-code) for targeting elements based on the language of their content
  • :dir(directionality), which selects elements with content in a specific directionality (e.g., left-to-right or right-to-left).
 p:lang(ko) {
   background-color: #FFFF00;
 }
 
 :root {
   background-color: #FAEBD7;
 }

nth-child vs nth-of-type Pseudo-Classes

One of the most challenging aspects of pseudo-classes is understanding the difference between the :nth-child and :nth-of-type pseudo-classes.

Both of these are structural pseudo-classes that target specific elements within a parent element (container), but they do so in distinct ways.

Assume n is 2. The :nth-child(n) selector targets an element that is the second child of its parent element, regardless of the type of element. On the other hand, :nth-of-type(n) targets the second occurrence of a specific type of element (e.g., a paragraph) within the parent element.

Let’s take a look at an example to illustrate this difference:

 /* Styles the second child element inside its parent, which can be of any type */
 p:nth-child(2) {
   color: #1E90FF; /* Light blue */
 }
 
 /* Styles the second paragraph inside its parent element */
 p:nth-of-type(2) {
   font-weight: bold;
 }

Now, let’s see how this CSS affects the HTML in two different scenarios.

Case 1

In Case 1, the second element inside a <div> is an unordered list, so the :nth-child(2) rule does not apply to the paragraphs. Although the unordered list is the second child, it is not a paragraph.

If, however, the parent element contains a second paragraph, the :nth-of-type(2) rule will apply, since this rule specifically targets <p> elements and ignores other types of elements (like unordered lists) within the parent element.

In our example, the :nth-of-type(2) rule will style the second paragraph, which is Child 3 in this case.

 <!-- Case 1 -->
 <div>
   <p>Paragraph 1, Child 1</p>
   <ul>Unordered List 1, Child 2</ul>
   <p>Paragraph 2, Child 3</p>
 </div>
Example of Nth-Pseudoclass Case 1
Case 2

In Case 2, we move the unordered list to the third position, placing the second paragraph before it. Now, both the :nth-child(2) and :nth-of-type(2) rules will apply, as the second paragraph is both the second child of its parent <div> and the second <p> element.

 <!-- Case 2 -->
 <div>
   <p>Paragraph 1, Child 1</p>
   <p>Paragraph 2, Child 2</p>
   <ul>Unordered List 1, Child 3</ul>
 </div>
Example of Nth-Pseudoclass Case 2

To explore the differences between :nth-child and :nth-of-type further, CSS Tricks has a great post on the topic. If you use SASS, Family.scss can help you create complex nth-child based elements.

Pseudo-Classes vs Pseudo-Elements

When discussing pseudo-classes, it’s crucial to understand how they differ from pseudo-elements to avoid confusion.

Pseudo-elements, like ::before and ::after (see this tutorial on how to use them), are also added by user agents and can be targeted and styled with CSS in a manner similar to pseudo-classes.

The key difference is that pseudo-classes are used to select HTML elements that already exist in the document tree, though they may not be explicitly marked, while pseudo-elements allow us to style elements that don’t typically exist in the DOM on their own. Examples include ::before and ::after, or parts of existing elements like ::first-letter and ::placeholder.

There is also a difference in syntax: pseudo-elements are generally written with double colons ::, while pseudo-classes use a single colon :. This can be confusing because, in CSS2, pseudo-elements were also marked with a single colon—browsers still support this older syntax.

Additionally, there are differences in how we can target pseudo-classes and pseudo-elements with CSS.

1. Their Place in the CSS Selector Sequence

Pseudo-elements must appear after the sequence of selectors, while pseudo-classes can be positioned anywhere within the CSS selector sequence.

For instance, you can target the last list item in a <ul> element in two different ways:

<ul>
  <li class="red"></li>
  <li></li>
  <li class="red"></li>
  <li class="red"></li>
</ul> 
ul > :last-child.red {
  color: #B0171F;
}

OR

ul > .red:last-child {
  color: #B0171F;
}

The first selector targets the last child inside the <ul> element that has the class .red, while the second selector targets the last child among the elements that possess the .red class inside <ul>. As you can see, the position of the pseudo-class can vary.

Let’s attempt something similar with pseudo-elements.

ul > .red::after {
  display: block;
  content: 'red';
  color: #B0171F;
}

The CSS above is valid, and the text “red” will appear after the <li> items with the class .red.

However, the following code will not work because we cannot change the position of a pseudo-element within the selector sequence.

ul > ::after.red {
  display: block;
  content: 'red';
  color: #B0171F;
}
2. Number of Occurrences in a Selector Sequence

Only one pseudo-element can be used per selector, whereas pseudo-classes can be combined as long as the combination makes sense. For example, to target first-child elements that are also read-only, you can combine the pseudo-classes :first-child and :read-only as follows:

:first-child:read-only {
  color: #EEEEEE;
}

jQuery Selector Extensions

Not all selector syntax that includes a : is a proper CSS pseudo-class. If you’ve used jQuery, you might be familiar with selectors like $(':checkbox'), $(':input'), and $(':selected').

It’s important to note that these are not CSS pseudo-classes being targeted by jQuery. Instead, they are known as jQuery selector extensions.

jQuery selector extensions allow you to target HTML elements with simpler keywords. Many of these extensions are shorthand for combinations of standard CSS selectors, represented by a single keyword.

/* Change the font of all input-related HTML elements,
   like button, select, and input */
 
$( ":input" ).css("font-family","courier new");
WebsiteFacebookTwitterInstagramPinterestLinkedInGoogle+YoutubeRedditDribbbleBehanceGithubCodePenWhatsappEmail