Andrew Spencer Creative problem solver working at the intersection of design and code. Frontend Designer at Sparkbox.

Advanced CSS selectors for common scenarios

4 min read 1275

CSS is weird. It’s unlike any other language that web developers use, yet it is critical to the visual representation of a website—the part that users interact with. In its most basic form, CSS is simple. For example, background-color: red means the background will be the color red. Yet, as anyone who has worked on a large CSS codebase will tell you, it can quickly become very complex and confusing.

CSS Grid, BEM, Dart Sass, there’s always something new to learn about CSS. It’s overwhelming! Instead of looking at what’s new, let’s focus on a fundamental building block of CSS—the selector. With a better understanding of the selector, and how to use it in more advanced ways, you can solve common scenarios with less code and cleaner CSS.

What is a selector?

Let’s make sure we’re on the same page. A CSS selector is how you can tell the browser what element to apply properties too. They can be general and apply to a large group of elements or be more specific and apply to only a single HTML element. There are five types of selectors:

  • Simple selectors for selecting HTML pieces such as div, #id, or .class
  • Combinator selectors which are based off code relationships like a “child” p > div or “adjacent sibling” div + div
  • Pseudo-class selectors to select a specific state of an element such as :hover, :first-child, or :nth-of-type
  • Pseudo-elements selectors to select specific parts of an element such as ::after, ::selection, or ::first-letter. Note how these always use double colon notation:: versus pseudo-class selectors which use a single colon
  • Attribute selectors to select elements with certain attributes such as ([class|="class-name"], [type="text"], or [target]

If two separate selectors are used to declare a style for the same element, the more specific selector will win—no matter what their order is in your CSS file. For example, a class selector like .class-name is more specific than a general HTML element selector such as span. If you need a refresher on how browsers decide which CSS styles to apply, also known as the CSS cascade, try this quiz.

Now that we understand what selectors are, let’s dive into how to use them effectively. You can take advantage of lesser-known selectors and even combine them to reduce the amount of code you need! Here are some more advanced selectors that I use in my CSS.

:nth-last-child(-n + 2)

There are many CSS pseudo-class selectors that can be used to select an element from a group of related elements—or children of a parent.

<parent>
    <child></child>
    <child></child>
</parent>

There’s :first-child, :nth-child, :nth-of-type, and the list goes on. These selectors become even more powerful when turned into expressions using numbers, the n counter, odd, or even. You can do math to select essentially any number of elements in a group. For example, n is a way to create algebraic expressions. So (2n+4) is equated as (2*n)+4 which will select every other element, starting with the fourth:

(2*0)+4 = 4
(2*1)+4 = 6
(2*2)+4 = 8
(2*4)+4 = 10

This is useful in many scenarios. For example, have you ever needed to style just the last two items in a list? You could add a class to those two elements which will result in more HTML and CSS, or you could use a single CSS selector, like this:

.item:nth-last-child(-n+2)

You can read this selector as “select the last two children of .item.” Looking at how this works, -n+2 equates to (-0+2)=2 or the second to last item, then (-1+2)=1 or the last item, then (-2+2)=0 which selects nothing, then (-3+2=-1) which, again, selects nothing, and so on for the total number of children elements. Only two elements are selected because of the negative sign in front of n which causes it to count down. If you want to select the last three items, -n+3 does just that. This allows us to select these elements without adding anything to the HTML!

See the Pen
Selector Demo – :nth-last-child(-n+2)
by Andrew Spencer (@iam_aspencer)
on CodePen.

We made a custom demo for .
No really. Click here to check it out.

:not(:last-child)

To take the last example even further, you can combine :last-child with the :not selector. This can help in scenarios where the last element in a group has a different style. Websites often call for a list of items with some space between them. A common problem with these scenarios is the need to remove spacing under the last item in the group.

selector demo
Oftentimes with CSS, it can feel natural to style the most common scenario first and then account for any outlying scenarios with a more specific selector. Then, the styles of the more specific selector will override the styles of the more general selector. Which, in our example, might result in something like this:

.image {
  margin-bottom: 2rem;

  &:last-child {
    margin-bottom: 0;
  }
}

Or you might have added an entirely new class to the HTML using a BEM CSS structure. Which would give you this:

.image {
  margin-bottom: 2rem;
}

.image--last {
  margin-bottom: 0;
}

Both paths result in seven lines of code and an overriding property. Not terrible, but if you think of your CSS as a programing language and “program” the logic right into the selector, you can reduce the CSS to this:

.image:not(:last-child) {
  margin-bottom: 2rem;
}

Reading the selector above, it says “select the image that is not the last child.” Three lines of code and no overriding styles—not bad!

See the Pen
Selector Demo – :nth-last-child(-n + 2)
by Andrew Spencer (@iam_aspencer)
on CodePen.

element:hover + adjacent sibling

Let’s look at a different scenario—hover states. Sometimes you need to adjust an element that is different than the element that is hovered. In JavasScript, this is fairly straightforward since JavaScript makes it easy to search for a specific element—also called traversing the DOM. Oftentimes though, with the right HTML structure, you only need CSS.

With CSS, you can use combinator selectors such as .parent > .child or .element + .general-sibling to target different elements. The one thing CSS cannot do is target an element that is a parent of the element that is hovered. Here’s a diagram of what CSS can target:

<div>
  <a>
    The element that is hovered
    <span>This is a child, CSS can style this</span>
  </a>
  <span>This is a sibling, CSS can style this</span>
  The "parent" <div> cannot be styled with CSS when the <a> is hovered
</div>

How might you use the powerful sibling selector? If you have two sibling elements and need to style the hover state of the second element, you can do so like this:

See the Pen
Selector Demo – :hover + sibling
by Andrew Spencer (@iam_aspencer)
on CodePen.

The sibling selector can also be used for form fields. With a label:hover + input selector, interacting with a <label> element can be used to highlight the <input> that is a sibling after the <label>.

See the Pen
Selector Demo – :hover + sibling link
by Andrew Spencer (@iam_aspencer)
on CodePen.

Your turn

Next time you think CSS is an inferior language, think again. It’s just… different. And that can be frustrating or rewarding depending on your point of view. Before you give up on CSS and resort to adding more HTML or blowing everything up with some JavaScript, take a look at the CSS selectors and properties you can use. Chances are there is one you can use to fit your needs.

 

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    Is your frontend hogging your users' CPU?

    As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.https://logrocket.com/signup/

    LogRocket is like a DVR for web apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

    Modernize how you debug web apps — .

    Andrew Spencer Creative problem solver working at the intersection of design and code. Frontend Designer at Sparkbox.

    Leave a Reply