CSS Selectors | CSS-Tricks
Anything with an equals sign (=
) followed by a value in that example code is an attribute. So, we can technically style all links with an href
attribute equal to https://css-tricks.com
:
a[href="https://css-tricks.com"] {
color: orangered;
}
Notice the syntax? We’re using square brackets ([]
) to select an attribute instead of a period or hashtag as we do with classes and IDs, respectively.
The equals sign used in attributes suggests that there’s more we can do to select elements besides matching something that’s exactly equal to the value. That is indeed the case. For example, we can make sure that the matching selector is capitalized or not. A good use for that could be selecting elements with the href
attribute as long as they do not contain uppercase letters:
/* Case sensitive */
a[href*='css-tricks' s] {}
The s
in there tells CSS that we only want to select a link with an href
attribute that does not contain uppercase letters.
...
...
If case sensitivity isn’t a big deal, we can tell CSS that as well:
/* Case insensitive */
a[href*='css-tricks' i] {}
Now, either one of the link examples will match regardless of there being upper- or lowercase letters in the href
attribute.
...
...
There are many, many different types of HTML attributes. Be sure to check out our Data Attributes guide for a complete rundown of not only [data-attribute]
but how they relate to other attributes and how to style them with CSS.
Universal selector
CSS-Tricks has a special relationship with the Universal Selector — it’s our logo!
That’s right, the asterisk symbol (*
) is a selector all unto itself whose purpose is to select all the things. Quite literally, we can select everything on a page — every single element — with that one little asterisk. Note I said every single element, so this won’t pick up things like IDs, classes, or even pseudo-elements. It’s the element selector for selecting all elements.
/* Select ALL THE THINGS! 💥 */
* {
/* Styles */
}
Or, we can use it with another selector type to select everything inside a specific element.
/* Select everything in an */
article * {
/* Styles */
}
That is a handy way to select everything in an
, even in the future if you decide to add other elements inside that element to the HTML. The times you’ll see the Universal Selector used most is to set border-sizing
on all elements across the board, including all elements and pseudo-elements.
*,
*::before,
*::after {
box-sizing: border-box;
}
There’s a good reason this snippet of CSS winds up in so many stylesheets, which you can read all about in the following articles.
Sometimes the Universal Selector is implied. For example, when using a pseudo selector at the start of a new selector. These are selecting exactly the same:
*:has(article) { }
:has(article) { }
Pseudo-selectors
Pseudo-selectors are for selecting pseudo-elements, just as element selectors are for selecting elements. And a pseudo-element is just like an element, but it doesn’t actually show up in the HTML. If pseudo-elements are new to you, we have a quick explainer you can reference.
Every element has a ::before
and ::after
pseudo-element attached to it even though we can’t see it in the HTML.
These are super handy because they’re additional ways we can hook into an element an apply additional styles without adding more markup to the HTML. Keep things as clean as possible, right?!
We know that ::before
and ::after
are pseudo-elements because they are preceded by a pair of colons (::
). That’s how we select them, too!
.container::before {
/* Styles */
}
The ::before
and ::after
pseudo-elements can also be written with a single colon — i.e., :before
and :after
— but it’s still more common to see a double colon because it helps distinguish pseudo-elements from pseudo-classes.
But there’s a catch when using pseudo-selectors: they require the content
property. That’s because pseudos aren’t “real” elements but ones that do not exist as far as HTML is concerned. That means they need content that can be displayed… even if it’s empty content:
.container::before {
content: "";
}
Of course, if we were to supply words in the content
property, those would be displayed on the page.
Article
on
Feb 4, 2022
Meet the Pseudo Class Selectors
Video
on
Feb 25, 2015
#94: Intro to Pseudo Elements
Article
on
Aug 29, 2018
::before vs :before
Article
on
Sep 21, 2021
7 Practical Uses for the ::before and ::after Pseudo-Elements in CSS
Article
on
Aug 3, 2021
Use Cases for Multiple Pseudo Elements
Article
on
Aug 19, 2021
A Whole Bunch of Amazing Stuff Pseudo Elements Can Do
Article
on
Jul 9, 2019
A Little Reminder That Pseudo Elements are Children, Kinda.
Article
on
Dec 14, 2020
One Invalid Pseudo Selector Equals an Entire Ignored Selector
Article
on
Sep 27, 2021
CSS Pseudo Commas
Article
on
Apr 16, 2013
List of Pseudo-Elements to Style Form Controls
Article
on
Oct 24, 2020
Animating the `content` Property
Article
on
May 31, 2017
Animating Single Div Art
Article
on
Jun 5, 2020
Text Wrapping & Inline Pseudo Elements
Article
on
Jul 25, 2011
3D Cube with One Element
Complex selectors
Complex selectors may need a little marketing help because “complex” is an awfully scary term to come across when you’re in the beginning stages of learning this stuff. While selectors can indeed become complex and messy, the general idea is super straightforward: we can combine multiple selectors in the same ruleset.
Let’s look at three different routes we have for writing these “not-so-complex” complex selectors.
Listing selectors
First off, it’s possible to combine selectors so that they share the same set of styles. All we do is separate each selector with a comma.
.selector-1,
.selector-2,
.selector-3 {
/* We share these styles! 🤗 */
}
You’ll see this often when styling headings — which tend to share the same general styling except, perhaps, for font-size
.
h1,
h2,
h3,
h4,
h5,
h6 {
color: hsl(25 80% 15%);
font-family: "Poppins", system-ui;
}
Adding a line break between selectors can make things more legible. You can probably imagine how complex and messy this might get. Here’s one, for example:
section h1, section h2, section h3, section h4, section h5, section h6,
article h1, article h2, article h3, article h4, article h5, article h6,
aside h1, aside h2, aside h3, aside h4, aside h5, aside h6,
nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 {
color: #BADA55;
}
Ummmm, okay. No one wants this in their stylesheet. It’s tough to tell what exactly is being selected, right?
The good news is that we have modern ways of combining these selectors more efficiently, such as the :is()
pseudo selector. In this example, notice that we’re technically selecting all of the same elements. If we were to take out the four section
, article
, aside
, and nav
element selectors and left the descendants in place, we’d have this:
h1, h2, h3, h4, h5, h6,
h1, h2, h3, h4, h5, h6,
h1, h2, h3, h4, h5, h6,
h1, h2, h3, h4, h5, h6, {
color: #BADA55;
}
The only difference is which element those headings are scoped to. This is where :is()
comes in handy because we can match those four elements like this:
:is(section, article, aside, nav) {
color: #BADA55;
}
That will apply color
to the elements themselves, but what we want is to apply it to the headings. Instead of listing those out for each heading, we can reach for :is()
again to select them in one fell swoop:
/* Matches any of the following headings scoped to any of the following elements. */
:is(section, article, aside, nav) :is(h1, h2, h3, h4, h5, h6) {
color: #BADA55;
}
While we’re talking about :is()
it’s worth noting that we have the :where()
pseudo selector as well and that it does the exact same thing as :is()
. The difference? The specificity of :is()
will equal the specificity of the most specific element in the list. Meanwhile, :where()
maintains zero specificity. So, if you want a complex selector like this that’s easier to override, go with :where()
instead.
Almanac
on
Dec 2, 2022
:is
Almanac
on
Jul 14, 2021
:where
Article
on
Apr 1, 2021
:where() has a cool specificity trick, too.
Article
on
Jun 10, 2020
CSS :is() and :where() are coming to browsers
Article
on
Apr 15, 2021
Platform News: Prefers Contrast, MathML, :is(), and CSS Background Initial Values
Article
on
Jul 12, 2021
Using the Specificity of :where() as a CSS Reset
Nesting selectors
That last example showing how :is()
can be used to write more efficient complex selectors is good, but we can do even better now that CSS nesting is a widely supported feature.
Desktop
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
120 | 117 | No | 120 | 17.2 |
Mobile / Tablet
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
126 | 127 | 126 | 17.2 |
CSS nesting allows us to better see the relationship between selectors. You know how we can clearly see the relationship between elements in HTML when we indent descendant elements?
CSS nesting is a similar way that we can format CSS rulesets. We start with a parent ruleset and then embed descendant rulesets inside. So, if we were to select the
element in that last HTML example, we might write a descendant selector like this:
article h2 { /* Styles */ }
With nesting:
article {
/* Article styles */
h2 { /* Heading 2 styles */ }
}
You probably noticed that we can technically go one level deeper since the heading is contained in another .article-content
element:
article {
/* Article styles */
.article-content {
/* Container styles */
h2 { /* Heading 2 styles */ }
}
}
So, all said and done, selecting the heading with nesting is the equivalent of writing a descendant selector in a flat structure:
article .article-content h2 { /* Heading 2 styles */ }
You might be wondering how the heck it’s possible to write a chained selector in a nesting format. I mean, we could easily nest a chained selector inside another selector:
article {
/* Article styles */
h2.article-content {
/* Heading 2 styles */
}
}
But it’s not like we can re-declare the article
element selector as a nested selector:
article {
/* Article styles */
/* Nope! 👎 */
article.article-element {
/* Container styles */
/* Nope! 👎 */
h2.article-content {
/* Heading 2 styles */
}
}
}
Even if we could do that, it sort of defeats the purpose of a neatly organized nest that shows the relationships between selectors. Instead, we can use the ampersand (&
) symbol to represent the selector that we’re nesting into. We call this the nesting selector.
article {
&.article-content {
/* Equates to: article.article-content */
}
}
Compounding selectors
We’ve talked quite a bit about the Cascade and how it determines which styles to apply to matching selectors using a specificity score. We saw earlier how an element selector is less specific than a class selector, which is less specific than an ID selector, and so on.
article { /* Specificity: 0, 0, 1 */ }
.featured { /* Specificity: 0, 1, 0 */ }
#featured { /* Specificity: 1, 0, 0 */ }
Well, we can increase specificity by chaining — or “compounding” — selectors together. This way, we give our selector a higher priority when it comes to evaluating two or more matching styles. Again, overriding ID selectors is incredibly difficult so we’ll work with the element and class selectors to illustrate chained selectors.
We can chain our article
element selector with our .featured
class selector to generate a higher specificity score.
article { /* Specificity: 0, 0, 1 */ }
.featured { /* Specificity: 0, 1, 0 */ }
articie.featured { /* Specificity: 0, 1, 1 */ }
This new compound selector is more specific (and powerful!) than the other two individual selectors. Notice in the following demo how the compound selector comes before the two individual selectors in the CSS yet still beats them when the Cascade evaluates their specificity scores.
Interestingly, we can use “fake” classes in chained selectors as a strategy for managing specificity. Take this real-life example:
.wp-block-theme-button .button:not(.specificity):not(.extra-specificity) { }
Whoa, right? There’s a lot going on there. But the idea is this: the .specificity
and .extra-specificity
class selectors are only there to bump up the specificity of the .wp-block-theme .button
descendant selector. Let’s compare the specificity score with and without those artificial classes (that are :not()
included in the match).
.wp-block-theme-button .button {
/* Specificity: 0, 2, 0 */
}
.wp-block-theme-button .button:not(.specificity) {
/* Specificity: 0, 3, 0 */
}
.wp-block-theme-button .button:not(.specificity):not(.extra-specificity {
/* Specificity: 0, 4, 0 */
}
Interesting! I’m not sure if I would use this in my own CSS but it is a less heavy-handed approach than resorting to the !important
keyword, which is just as tough to override as an ID selector.
Combinators
If selectors are “what” we select in CSS, then you might think of CSS combinators as “how” we select them. they’re used to write selectors that combine other selectors in order to target elements. Inception!
The name “combinator” is excellent because it accurately conveys the many different ways we’re able to combine selectors. Why would we need to combine selectors? As we discussed earlier with Chained Selectors, there are two common situations where we’d want to do that:
- When we want to increase the specificity of what is selected.
- When we want to select an element based on a condition.
Let’s go over the many types of combinators that are available in CSS to account for those two situations in addition to chained selectors.
Descendant combinator
We call it a “descendant” combinator because we use it to select elements inside other elements, sorta like this:
/* Selects all elements in .parent with .child class */
.parent .child {}
…which would select all of the elements with the .child
class in the following HTML example:
See that element with the .friend
classname? That’s the only element inside of the .parent
element that is not selected with the .parent .child {}
descendant combinator since it does not match .child
even though it is also a descendant of the .parent
element.
Child combinator
A child combinator is really just an offshoot of the descendant combinator, only it is more specific than the descendant combinator because it only selects direct children of an element, rather than any descendant.
Let’s revise the last HTML example we looked at by introducing a descendant element that goes deeper into the family tree, like a .grandchild
:
So, what we have is a .parent
to four .child
elements, one of which contains a .grandchild
element inside of it.
Maybe we want to select the .child
element without inadvertently selecting the second .child
element’s .grandchild
. That’s what a child combinator can do. All of the following child combinators would accomplish the same thing:
/* Select only the "direct" children of .parent */
.parent > .child {}
.parent > div {}
.parent > * {}
See how we’re combining different selector types to make a selection? We’re combinating, dangit! We’re just doing it in slightly different ways based on the type of child selector we’re combining.
/* Select only the "direct" children of .parent */
.parent > #child { /* direct child with #child ID */
.parent > .child { /* direct child with .child class */ }
.parent > div { /* direct child div elements */ }
.parent > * { /* all direct child elements */ }
It’s pretty darn neat that we not only have a way to select only the direct children of an element, but be more or less specific about it based on the type of selector. For example, the ID selector is more specific than the class selector, which is more specific than the element selector, and so on.
General sibling combinator
If two elements share the same parent element, that makes them siblings like brother and sister. We saw an example of this in passing when discussing the descendant combinator. Let’s revise the class names from that example to make the sibling relationship a little clearer:
This is how we can select the .sister
element as long as it is preceded by a sibling with class .brother
.
/* Select .sister only if follows .brother */
.brother ~ .sister { }
The Tilda symbol (~
) is what tells us this is a sibling combinator.
It doesn’t matter if a .sister
comes immediately after a .brother
or not — as long as a .sister
comes after a brother
and they share the same parent element, it will be selected. Let’s see a more complicated HTML example:
The sibling combinator we wrote only selects the first three .sister
elements because they are the only ones that come after a .brother
element and share the same parent — even in the case of the third .sister
which comes after another sister! The fourth .sister
is contained inside of a .cousin
, which prevents it from matching the selector.
Let’s see this in context. So, we can select all of the elements with an element selector since each element in the HTML is a div
:
From there, we can select just the brothers with a class selector to give them a different background color:
We can also use a class selector to set a different background color on all of the elements with a .sister
class:
And, finally, we can use a general sibling combinator to select only sisters that are directly after a brother.
Did you notice how the last .sister
element’s background color remained green while the others became purple? That’s because it’s the only .sister
in the bunch that does not share the same .parent
as a .brother
element.
Adjacent combinator
Believe it or not, we can get even more specific about what elements we select with an adjacent combinator. The general sibling selector we just looked at will select all of the .sister
elements on the page as long as it shares the same parent as .brother
and comes after the .brother
.
What makes an adjacent combinator different is that it selects any element immediately following another. Remember how the last .sister
didn’t match because it is contained in a different parent element (i.e., .cousin
)? Well, we can indeed select it by itself using an adjacent combinator:
/* Select .sister only if directly follows .brother */
.brother + .sister { }
Notice what happens when we add that to our last example:
The first two .sister
elements changed color! That’s because they are the only sisters that come immediately after a .brother
. The third .sister
comes immediately after another .sister
and the fourth one is contained in a .cousin
which prevents both of them from matching the selection.