Lesson Weekend

As a human, you can exist in a wide variety of states (tired? hungry? full? excited? frustrated?). But did you know DOM elements can exist in a variety of states too?!

Consider an <a href> link. It can be in the state of being actively hovered over by the cursor, or the state of already being clicked on/visited by the user. Similarly, a child <span> inside a parent element can be in the state of being the first element in that parent, or the state of being the last.

CSS includes tools to alter the appearance and behavior of elements based not on their class, ID, or tag type, but on their on states like these! They're called pseudo-classes, and they offer decidedly rad functionality. Let's check 'em out.

Pseudo-Classes

You may have seen pseudo-classes in the wild. They're special keywords preceded with a colon attached to a CSS selector. As explained in the Pseudo-Classes documentation entry in the MDN their syntax looks like this:

selector:pseudo-class {
  property: value;
}

Here's an example pseudo-class in action:

.rad-content:hover {
  background-color: green;
}

This rule is applied to anything with the class of rad-content but only when the user's cursor is actively hovering over it.

Let's check out another example:

a:visited {
  color: purple;
}

This rule turns the text of <a> links purple, but only if the user has already visited them. This style would not be applied to links not yet visited.

In both of these examples, the relevant pseudo classes (:hover and :visted) describe a state the element they're attached to (a and rad-content class) can be in. When a CSS selector includes a pseudo class like it's called a pseudo-selector.

Let's explore what other pseudo classes are built into CSS.

General

  • :hover selects the element the cursor is currently hovering above, if any. In the following example we turn any <li> elements with the class sample-item blue, but only when the cursor is actively hovering above them:
li.sample-item:hover {
  color: blue;
}
  • :empty selects elements that don't contain any content. Like <div></div> or <p></p>. For example, the following would hide any <div> elements with a class of dynamic-content if/when they are empty:
div.dynamic-content:empty {
  display: none;
}
  • :active selects the element currently being pressed down upon by a mouse click (or a finger on touchscreens). The following rule would make any <button> temporarily more transparent during the moment the user's mouse is clicking on it:
button:active {
  opacity: 0.5;
}

The following can be used to assign styles to link (<a>) elements based on their state:

  • :link selects link(s) the user has not yet visited. For instance, the rule below would turn any un-clicked <a> links pink:
a:link {
  color: pink;
}
  • :visited selects link(s) the user has already visited. The following would turn visited links purple:
a:visited {
  color: purple;
}

Form Fields

These next pseudo-classes are used to assign state-based styles to form fields:

  • :focus selects element(s) (usually form inputs) that are in the current focus. You know how you can hit the Tab key to run through fields in a form? When you do this, the currently-selected field is referred to as being in focus. The following would give any <input> fields in focus a blue background:
input:focus {
  background-color: deepskyblue;
}
  • :enabled selects inputs that are enabled and ready to be used. (That is, they haven't had the disabled property applied to them.)

  • :disabled selects inputs with the disabled attribute.

  • :checked selects currently-checked checkboxes.

  • :required selects form fields marked as required.

  • :optional selects inputs that do not have a required field.

Structure & Position

This next series of pseudo-classes helps select elements based on their position relative to the page, parents, and/or siblings:

  • :root selects root element of the document, which unless you're doing something specialized, is almost always <html>.

  • :first-child selects the first element inside a parent. Note that this pseudo-element should be used in conjunction with the type of child you want to alter, not the type of parent you want to alter. For instance, if we had the following HTML:

<div class="content">
  <p>First item!</p>
  <p>Second item!</p>
</div>

And we wanted to turn the text reading "First item!" yellow, we could use this rule:

p:first-child {
  color: yellow;
}

Notice we don't call div:first-child to specify the first child of the parent div. Instead, we call p:first-child which selects all <p> elements that are first-appearing children of their parent.

  • :last-child works similarly to :first-child, but selects the last element inside a parent.

  • :only-of-type selects the specified element but only if it's the only one of its type within the current parent. For example, the following rule would apply a red background to all <p> tags that are the only <p> tags in their respective parents:

p:only-of-type {
  background-color: red;
}
  • :first-of-type selects the first element of the specified type within any parent. So if we had the following html:
<div>
  <p>Content!</p>
  <img src="sample/image/filepath/first-image.jpeg">
  <img src="sample/image/filepath/second-image.jpeg">
</div>
<div>
  <p>And here's some <span class="emphasis">more</span> content!</p>
  <img src="sample/image/filepath/third-image.jpeg">
  <img src="sample/image/filepath/fourth-image.jpeg">
</div>

And added following CSS rule:

img:first-of-type {
  border-radius: 10px;
}

The first-image.jpeg and third-image.jpeg images would both have rounded edges. Why? Because they're the first child of their type in each of their respective <div> parents.

  • :last-of-type works almost exactly like the :first-of-type pseudo-class, but selects the last element of the specified type within any parent.

Algebraic

This next set of pseudo-classes is more complex. Notice the selectors below have parenthesis after them. This is not a mistake. Similar to a programmatic function, these selectors actually take arguments. Let's check 'em out:

  • :nth-child() selects children elements based on a provided as an argument. It takes three different types of arguments:
    1. Numbers
    2. Keywords
    3. Algebraic expressions

Numbers

Let's assume we have HTML like this:

...
<div class="facts">
  <div class="info-tile">1</div>
  <div class="info-tile">2</div>
  <div class="info-tile">3</div>
  <div class="info-tile">4</div>
  <div class="info-tile">5</div>
  <div class="info-tile">6</div>
  <div class="info-tile">7</div>
  <div class="info-tile">8</div>
</div>
...

We could change the height of only the third info-tile div like this:

.info-tile:nth-child(3){
  height: 200px;
}

By providing the 3 as an argument, we target the third-appearing child with the info-tile class.

Keywords

:nth-child() also accepts two keywords as arguments: odd and even. Using the same example HTML above, we could select every even div in the list with the following:

.info-tile:nth-child(even){
  height: 200px;
}

Or, every odd div like this:

.info-tile:nth-child(odd){
  height: 200px;
}

Algebraic Expressions

The third type of argument :nth-child() accepts is an algebraic expression in the following format:

an+b
  • a can be any whole number (positive or negative).
  • b may also be any whole number (positive or negative).
  • n is always the literal letter n.

The SiteCast article nth-child and nth-of-type explains how this formula works:

"When the expression, in the format an+b contains non-zero values for a and b, the child elements are split into groups of a elements.

If the expression was 2n+1, the child elements would be split into groups of 2. Each element in the group is then given an index, starting at 1. The matched element in each group is bth index. In this example, that would be the first element.

If the expression was 3n+2, the list items would be grouped into sets of 3 and the second item in each group would be matched.

If the value of b is negative, the matched element in the group is the bth index but counted backwards from index 1. In this instance, the matched element from a group will no longer match an element in that group, but in one above it."

So, for instance, the following would select every third info-tile child div, but it would begin counting at the second element:

.info-tile:nth-child(3n+2){
  height: 200px;
}

Whereas this rule would select every fourth info-tile child div, but begin counting at the 6th element:

.info-tile:nth-child(4n+6){
  height: 200px;
}

Also, if we simply wanted to begin counting children at the beginning, we could omit the +b portion of the expression entirely. The following rule, for example, would select every third child, beginning at the top:

.info-tile:nth-child(3n){
  height: 200px;
}

You can check out other examples of :nth-child() usages in the CSS-Tricks article Useful :nth-child Recipes. You can also see a visual representation of :nth-child() expressions in action using the interactive CSS-Tricks :nth Tester

More Algebraic Pseudo-Classes

  • :nth-last-child() works the same as :nth-child, but counts up from the bottom instead of the top.

  • :nth-of-type() works like :nth-child, but is meant for when the elements at the same level are of different types. For instance, if a div had a variety of paragraphs, images, other divs, etc. within it, and we wanted to select every other image, :nth-child() wouldn't work, because it would consider all the children, and not only the images. Instead, we'd could use div img:nth-of-type(odd).

  • :nth-last-of-type() works the same as :nth-of-type, but counts up from the bottom instead of the top.

Additional Resources

For more information about pseudo-classes, we recommend the following resources:

  • Both this nth-Test sandbox and CSS-Tricks' :nth-tester allow us to test out which elements our :nth-child algebraic expressions will select before integrating them into our codebases.

  • The MDN Documentation on Pseudo-Classes offers a comprehensive list of all pseudo-classes, including several less common ones not covered here.

Lesson 26 of 36
Last updated more than 3 months ago.