The Definitive Guide to CSS Pseudo-Classes

Whether you are a novice or an experienced CSS developer, you probably have heard of pseudo-classes. The most well known pseudo-class is probably :hover, which enables us to style an element when it’s in the hover state, i.e. when a pointer device, such as a mouse, is pointed at it.

Following the concept of our previous posts on margin:auto and CSS Floats, we take a closer in-depth look at pseudo-classes in this post. We will see what pseudo-classes really are, how they work, how we can categorize them, and how they are different from pseudo-elements.

What Are Pseudo-Classes?

A pseudo-class is a keyword we can add to CSS selectors in order to define a special state of the belonging HTML element. We can add a pseudo-class to a CSS selector by using the colon syntax : like this: a:hover{ ... }

A CSS class is an attribute we can add to HTML elements we want to apply the same style rules for, such as top menu items or titles of sidebar widgets. In other words, we can use CSS classes to group or classify HTML elements that are similar one way or the other.

Pseudo-classes are similar to them in the sense that they are also used to add style rules to the elements that share the same characteristic.

But while genuine classes are user-defined and can be spotted in the source code, for instance <div class="myClass">, pseudo-classes are added by UA (user agents), like web browsers, based on the current state of the belonging HTML-element.

Purpose of Pseudo-Classes

The job of regular CSS classes is to classify or group elements. Developers know how their elements are to be grouped: they can form classes like "menu-items", "buttons", "thumbnails", etc. to group, and later style similar elements. These classifications are based on the elements’ characteristics that are given by the developers themselves.

For instance, if a developer decides to use a <div> as a thumbnail object she or he can classify that <div> with a "thumbnail" class.

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

HTML elements however have their own common characteristics based on their state, position, nature, and interaction with the page and the user. These are the characteristics that are not typically marked in the HTML code, but we can target them with pseudo-classes in CSS, for example:

  • an element that is the last child inside its parent element
  • a link that is visited
  • an element that has gone fullscreen.

These are the kind of characteristics that are generally targeted by the pseudo classes. To understand the difference between classes and pseudo-classes better, let’s assume we’re using the class .last to identify the last elements in different parent containers.

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

    <option>option 1</option>
    <option>option 2</option>
    <option>option 3</option>
    <option class="last">option 4</option>

We can style these last child elements with the following CSS:

li.last {
  text-transform: uppercase;


But what happens when the last element changes? Well, we will have to move the .last class from the former last element to the current one.

This hassle of updating classes can be left to the user agent, at least for those characteristics that are common among elements (and being a last element is as common as it can get). Having a pre-defined :last-child pseudo-class is very useful indeed. This way, we don’t have to indicate the last element in the HTML-code, but we can still style them with the following CSS:

li:last-child {
  text-transform: uppercase;

option:last-child {

Main Types of Pseudo-Classes

There are many kinds of pseudo-classes, all of them provide us with ways to target elements based on their features that are otherwise inaccessible or trickier to access. Here’s a list of standard pseudo classes in MDN.

1. Dynamic Pseudo-Classes

Dynamic pseudo-classes are added to and removed from HTML elements dynamically, based on the state they transition into in response to the user’s interactions. Some of the examples of dynamic pseudo-classes are :hover, :focus, :link, and :visited, all of which can be added to the <a> anchor tag.

  color: #8D20AE;
  font-weight: bold;

2. State-Based Pseudo-Classes

State-based pseudo-classes are added to elements when they are in a particular static state. Some of its most well-known examples are:

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

The most popular state based pseudo-class has got to be :checked, which flags whether a checkbox is checked or not.

.checkbox:checked + label{
  background-color: #EEEEEE;

3. Structural Pseudo-Classes

Structural pseudo-classes classify elements based on their position in the document structure. Its most common examples are :first-child, :last-child, and :nth-child(n) – all can be used to target a specific child element inside a container based on its position – and :root which targets the highest-level parent element in the DOM.

4. Miscellaneous Pseudo-Classes

There are also miscellaneous pseudo-classes that are hard to classify, such as:

  • :not(x) which selects elements that don’t match the selector x
  • :lang(language-code) that selects elements of which content is in a specific language
  • :dir(directionality) that selects elements with content of a given directionality (left-to-right or right-to-left).
  background-color: #FFFF00;
  background-color: #FAEBD7;

nth-child vs nth of type Pseudo-Classes

One of the hardest things about pseudo-classes is probably to understand the difference between the :nth-child and :nth-of-type pseudo-classes.

Both are structural pseudo-classes, and mark a specific element inside a parent element (container), but in a different way.

Assume n is 2, then :nth-of-child(n) targets an element that is the second child of its parent element, and :nth-of-type(n) targets the second among the same type of element (such as paragraphs) inside a parent element.

Let’s have a look at an example.

/* a paragraph that's also the second child inside its parent element */
  p:nth-child(2) {
  color: #1E90FF;    // lightblue
/* the second paragraph inside a parent element */
p:nth-of-type(2) {

Let’s see how this short CSS style the HTML in two different cases.

Case 1

In Case 1, the second element inside a <div> is an unordered list, therefore the nth-child(2) rule won’t apply to it. Although it’s a second child, it is not a paragraph.

But if the parent element does have a second paragraph, the nth-of-type(2) rule will apply, as this rule only looks for the <p> elements, and doesn’t care about other types of elements (such as unordered lists) inside the parent element.

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

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

Case 2

In the second case, we move the unordered list to the third place, and the second paragraph will come before it. This means that both the :nth-child(2) and the :nth-of-type(2) rules will be applied, as the second paragraph is also the second child of its parent <div> element.

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

If you want to read more on the differences between the :nth-of-child and :nth-of-type pseudo-classes, CSS Tricks has a great post on it. If you use SASS, the Family.scss can help you create complicated nth-child‘ified elements.

Pseudo-Classes vs Pseudo-Elements

When we speak about pseudo-classes, it’s also important to understand how they differ from pseudo-elements, in order not to mix them up.

Pseudo-elements, such as ::before and ::after (see this tutorial on how to use them) are also added by user agents, and they can be targeted and styled with CSS as well, just like pseudo-classes.

But while we use pseudo-classes to select HTML elements that exist in the document tree just not marked separately, pseudo-elements allow us to target elements that don’t normally exist in the DOM, either at all (eg ::before and ::after) or only as certain parts of existing elements (eg ::first-letter or ::placeholder).

There’s also a difference in syntax. Pseudo-elements are generally identified with double colons ::, whereas pseudo-classes are identified with a single colon :.

This can lead to a case of confusion as in CSS2, pseudo-elements were marked with a single colon as well – browsers still accept the single colon syntax for pseudo-elements.

There are also differences between pseudo-classes and pseudo-elements in the way we can target them with CSS.

1. Their Place in the CSS Selector Sequence

Pseudo-elements can only appear after the sequence of selectors, whereas pseudo-classes can be placed anywhere in the CSS selector sequence.

For example, you can target the last list item of a list element like <ul> in two ways.

  <li class="red"></li>
  <li class="red"></li>
  <li class="red"></li>
ul > {
  color: #B0171F;


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

The first sequence of the selector selects the last child inside the <ul> element (that has the class .red) and the second one selects the last child among elements that possess the .red class inside <ul>. As you can see, the position of the pseudo-class is changeable.

Let’s try to do something similar with pseudo-elements.

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

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

This code on the other hand, won’t work, as we cannot change the position of a pseudo-element inside the selector sequence.

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

2. Number of Occurrences in a Selector Sequence

Also, only one pseudo-element can appear beside a selector, whereas pseudo-classes can be combined with each other if the combination makes sense. For instance, to target first child elements that are also read-only, we can create a combination of the pseudo-classes :first-child and :read-only in the following way:

:first-child:read-only {

jQuery Selector Extensions

A selector code with a : syntax doesn’t always constitute a proper CSS pseudo-class. If you have ever used jQuery, then you might have used many of its selectors with : syntax, for example $(':checkbox'), $(':input') and $(':selected').

It’s important to know that these are not CSS pseudo-classes that are being targeted by jQuery. They are called jQuery selector extensions.

jQuery selector extensions lets you target HTML elements with simpler keywords. Most of them are combinations of multiple normal CSS selectors, which are represented with a single keyword.

/* Change the font of all input-related HTML elements,
like button, select, and input */

$( ":input" ).css("font-family","courier new")