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.
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:
div
, #id
, or .class
p > div
or “adjacent sibling” div + div
:hover
, :first-child
, or :nth-of-type
::after
, ::selection
, or ::first-letter
. Note how these always use double colon notation::
versus pseudo-class selectors which use a single colon[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.
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.
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.
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.
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.
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.
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.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. 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 and mobile apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe use cases for the ResizeObserver API may not be immediately obvious, so let’s take a look at a few practical examples.
Automate code comments using VS Code, Ollama, and Node.js.
Learn to build scalable micro-frontend applications using React, discussing their advantages over monolithic frontend applications.
Build a fully functional, real-time chat application using Laravel Reverb’s backend and Vue’s reactive frontend.