A Deep Dive Into CSS Selectors

April 12, 2021

Introduction

Since we’ve covered the basics of selectors, we’ll take some time to go into some of the more advanced ways to select elements in CSS.

Advanced Selectors

Things can get a bit messy and limiting when you’re selecting only classname and element type. Thats where grouping, combinators, pseudo-elements, and pseudo-selectors come into play.

Grouping Selectors

What if I want my rule to apply to more than one thing, what gives? Well, have I got a treat for you! You can easily group selectors by simply by separating them by a comma. Take this for example:

.button-forward {
    width: 400px;
    height: 50px;
}

.button-backward {
    width: 400px;
    height: 50px;
}

We can combine these two rules into a single rule like so:

.button-forward, .button-backward {
    width: 400px;
    height: 50px;
}

The Pseudos

Some of the handiest features of CSS are pseudo classes and pseudo elements. While they aren’t selecting actual DOM elements, they are an additional level of specificity when selecting elements in particular states.

Pseudo-classes

Pseudo-classes, as their name implies, are not actually classes at all. They are simply keywords that can be added to selectors to specify a particular state of that selected element.

One of the more popular pseudo-classes is :hover. This pseudo class is used to select the state an element enters when the mouse is hovered over it. It’s particularly useful when you want to change the color of a link while hovering over it.

<a href="#" class="link--home">Home</a>
.link--home {
    color: red;
}

.link--home:hover {
    color: pink;
}

The example above will render a red link while not hovered, and a pink link while hovered.

There are a ton of pseudo selectors! For a full list, check out pseudo-selectors on MDN. Some of the ones I find myself using very frequently are :first-child, :last-child, :hover, :enabled, and :disabled.

Pseudo-elements

Much like pseudo-classes, pseudo elements are special keywords that can be added to selectors that also let you style particular parts of the element. The main difference between pseudo-classes, is that pseudo-classes allow you to style an element based on its state, while pseudo-elements are special non-DOM elements associated with DOM elements.

When using pseudo elements, it is a best practice to prepend them with double colons :: rather than single colons : as pseudo-classes do. While this is not strictly enforced in most browsers, it helps with readability and distinction.

Two of the most popular pseudo-elements are the ::before and ::after pseudo-elements. These selectors create pseudo-elements in the DOM that are either the first-child or last-child of the selected element respectively.

<blockquote>So long, and thanks for all the fish!</blockquote>

Our markup before CSS is pretty plain.

Screen Shot 2021-04-11 at 6.37.55 PM.png

/* To keep our code DRY, we can apply common styles
to both elements with a single grouping selector */

blockquote::before,
blockquote::after {
    font-size: 3rem;
    color: red;
}

blockquote::before {
    content: "“";
}

blockquote::after {
    content: "”"
}

blockquote {
    color: purple;
}

As a result, we get extra large red quotes appended and prepended! We have also changed the color of our base element to purple.

Screen Shot 2021-04-11 at 6.37.45 PM.png

Selector Combinators

Sometimes you just need to get a little more specific with your selectors, and that’s where combinators come into play. Combinators are simply ways to spice up your selectors by adding a little bit of specificity.

Combinator List

We’ll go through each type of combinator in a bit of detail.

Descendant

The descendent combinator is denoted by a space . It selects all nodes that are descendants of the element before the space.

Example
<div class="banana">
    <p>I am a banana!</p>
    <div>
        <p>I am a banana too!</p>
    </div>
</div>

<div class="orange">
    <p>I am an orange.</p>
    <div>
        <p>I am an orange too.</p>
    </div>
</div>

Screen Shot 2021-04-11 at 6.38.25 PM.png

Now, we have four different p elements; two as descendants of the div with a class of .banana, and two as descendants of the div with a class of .orange. Here’s how we select them!

.banana, .orange {
    background-color: gray;
}

.banana p {
    color: yellow;
}

.orange p {
    color: orange;
}

Screen Shot 2021-04-11 at 6.44.09 PM.png

Child

The child combinator, denoted by a greater than symbol > , works in a very similar way to descendent combinator, except that it only selects elements that are direct children of the element before the operator.

Example

Using the same HTML as the previous example, we could edit our CSS in such a way as to only select the direct children p tags of the .banana and .orange classes.

.banana, .orange {
    background-color: gray;
}

.banana > p {
    color: yellow;
}

.orange > p {
    color: orange;
}

As you can see, our second <p> element in each respective div did not have its colors changed by the selector.

Screen Shot 2021-04-11 at 6.43.58 PM.png

General Sibling

The general sibling combinator, with a symbol of ~, will select all sibling elements after the element before the selector. It is similar to the descendant combinator, but instead descendants, it selects siblings.

Example

This time we’ll change up our HTML just a bit to show the full effect of the combinator.

<div class="banana">
    <div>
        <p>I am an imposter! Orange!</p>
    </div>

    <p>I am a banana!</p>

    <div>
        <p>I am a banana too!</p>
    </div>
</div>

<div class="orange">
    <div>
        <p>I am an imposter! Banana!</p>
    </div>

    <p>I am an orange.</p>

    <div>
        <p>I am an orange too.</p>
    </div>
</div>

With our previous CSS, our markup renders like this:

Screen Shot 2021-04-11 at 6.50.46 PM.png

So, let’s change up our CSS a bit and use some of the stuff we learned earlier as well.

.banana, .orange {
    background-color: gray;
}

/* Here we select any DIV
that is the FIRST CHILD (div:first-child)
and DIRECT DESCENDANT (>)
of any element that has a class
of BANANA or ORANGE (.banana .orange) */
.banana > div:first-child,
.orange > div:first-child {
    color: crimson;
}

/* Reading backwards through this selector,
of any element type (*)
that is a sibling of and comes after (~) 
a div that is first of its siblings (div:first-child)
and an immediate child (>)
of any element with the classes of
.banana or .orange */

.banana > div:first-child ~ * {
    color: yellow;
}
.orange > div:first-child ~ * {
    color: orange;
}

Screen Shot 2021-04-11 at 7.07.36 PM.png

Adjacent Sibling

The adjacent sibling + is very similar to the general sibling combinator, but instead of selecting all instances of the element that comes after the combinator, it only selects the first instance.

Example

Here we’ll use the previous HTML, and change up the CSS just a little bit.

.banana, .orange {
    background-color: gray;
}

/* Here we select any DIV
that is the FIRST CHILD (div:first-child)
and DIRECT DESCENDANT (>)
of any element that has a class
of BANANA or ORANGE (.banana .orange) */
.banana > div:first-child,
.orange > div:first-child {
    color: crimson;
}

/* Reading backwards through this selector,
of any element type (*)
that is a the first of that type after (+) 
a div that is first of its siblings (div:first-child)
and an immediate child (>)
of any element with the classes of
.banana or .orange */

.banana > div:first-child + * {
    color: yellow;
}
.orange > div:first-child + * {
    color: orange;
}

Screen Shot 2021-04-11 at 7.10.23 PM.png

And as you can see in the example, we’ve only changed the colors of the text in the divs after the selected element.

Wrapping up

I hope this has enlightened you a bit as to how to be more efficient when selecting specific elements in CSS. Theres still plenty more to cover, so stay tuned for the next installment!

Header photo by James Harrison on Unsplash.