Overview
CSS is really good at many things, but it’s really, really good at two specific things: selecting elements and styling them. That’s the raison d’être for CSS and why it’s a core web language. In this guide, we will cover the different ways to select elements — because the styles we write are pretty much useless without the ability to select which elements to apply them to.
The source of truth for CSS selectors is documented in the Selectors Module Level 4 specification. With one exception (which we’ll get to), all of the selectors covered here are well-covered by browsers across the board, and most certainly by all modern browsers.
In addition to selectors, this guide also looks at CSS combinators. If selectors identify what we are selecting, you might think of combinators as how the styles are applied. Combinators are like additional instructions we give CSS to select a very particular element on the page, not totally unlike the way we can use filters in search engines to find the exact result we want.
Quick reference
Common Selectors
/* Universal */
* {
box-sizing: border-box;
}
/* Type or Tag */
p {
margin-block: 1.5rem;
}
/* Classname */
.class {
text-decoration: underline;
}
/* ID */
#id {
font-family: monospace;
}
/* Relational */
li:has(a) {
display: flex;
}
Common Combinators
/* Descendant */
header h1 {
/* Selects all Heading 1 elements in a Header element. */
}
/* Child */
header > h1 {
/* Selects all Heading 1 elements that are children of Header elements. */
}
/* General sibling */
h1 ~ p {
/* Selects a Paragraph as long as it follows a Heading 1. */
}
/* Adjacent sibling */
h1 + p {
/* Selects a Paragraph if it immediately follows a Heading 1 */
}
/* Chained */
h1, p {
/* Selects both elements. */
}
General Selectors
When we talk about CSS selectors, we’re talking about the first part of a CSS ruleset:
/* CSS Ruleset */
selector {
/* Style rule */
property: value;
}
See that selector? That can be as simple as the HTML tag we want to select. For example, let’s select all <article> elements on a given page.
/* Select all <article> elements... */
article {
/* ... and apply this background-color on them */
background-color: hsl(25 100% 50%);
}
That’s the general process of selecting elements to apply styles to them. Selecting an element by its HTML tag is merely one selector type of several. Let’s see what those are in the following section.
Element selectors
Element selectors are exactly the type of selector we looked at in that last example: Select the element’s HTML tag and start styling!
That’s great and all, but consider this: Do you actually want to select all of the <article> elements on the page? That’s what we’re doing when we select an element by its tag — any and all HTML elements matching that tag get the styles. The following demo selects all <article> elements on the page, then applies a white (#fff) background to them. Notice how all three articles get the white background even though we only wrote one selector.
I’ve tried to make it so the relevant for code for this and other demos in this guide is provided at the top of the CSS tab. Anything in a @layer can be ignored. And if you’re new to @layer, you can learn all about it in our CSS Cascade Layers guide.
But maybe what we actually want is for the first element to have a different background — maybe it’s a featured piece of content and we need to make it stand out from the other articles. That requires us to be more specific in the type of selector we use to apply the styles.
Let’s turn our attention to other selector types that allow us to be more specific about what we’re selecting.
ID selectors
ID selectors are one way we can select one element without selecting another of the same element type. Let’s say we were to update the HTML in our <article> example so that the first article is “tagged” with an ID:
<article id="featured">
<!-- Article 1 -->
</article>
<article>
<!-- Article 2 -->
</article>
<article>
<!-- Article 3 -->
</article>
Now we can use that ID to differentiate that first article from the others and apply styles specifically to it. We prepend a hashtag character (#) to the ID name when writing our CSS selector to properly select it.
/* Selects all <article> elements */
article {
background: #fff;
}
/* Selects any element with id="featured" */
#featured {
background: hsl(35 100% 90%);
border-color: hsl(35 100% 50%);
}
There we go, that makes the first article pop a little more than the others!
Before you go running out and adding IDs all over your HTML, be aware that IDs are considered a heavy-handed approach to selecting. IDs are so specific, that it is tough to override them with other styles in your CSS. IDs have so much specificity power than any selector trying to override it needs at least an ID as well. Once you’ve reached near the top of the ladder of this specificity war, it tends to lead to using !important rules and such that are in turn nearly impossible to override.
Let’s rearrange our CSS from that last example to see that in action:
/* Selects any element with id="featured" */
#featured {
background: hsl(35 100% 90%);
border-color: hsl(35 100% 50%);
}
/* Selects all <article> elements */
article {
background: #fff;
}
The ID selector now comes before the element selector. According to how the CSS Cascade determines styles, you might expect that the article elements all get a white background since that ruleset comes after the ID selector ruleset. But that’s not what happens.
So, you see how IDs might be a little too “specific” when it comes to selecting elements because it affects the order in which the CSS Cascade applies styles and that makes styles more difficult to manage and maintain.
The other reason to avoid IDs as selectors? We’re technically only allowed to use an ID once on a page, per ID. In other words, we can have one element with #featured but not two. That severely limits what we’re able to style if we need to extend those styles to other elements — not even getting into the difficulty of overriding the ID’s styles.
A better use case for IDs is for selecting items in JavaScript — not only does that prevent the sort of style conflict we saw above, but it helps maintain a separation of concerns between what we select in CSS for styling versus what we select in JavaScript for interaction.
Another thing about ID selectors: The ID establishes what we call an “anchor” which is a fancy term for saying we can link directly to an element on the page. For example, if we have an article with an ID assigned to it:
<article id="featured">...</article>
…then we can create a link to it like this:
<a href="featured">Jump to article below ⬇️</a>
<!-- muuuuuuch further down the page. -->
<article id="featured">...</article>
Clicking the link will navigate you to the element as though the link is anchored to that element. Try doing exactly that in the following demo:
This little HTML goodie opens up some pretty darn interesting possibilities when we sprinkle in a little CSS. Here are a few articles to explore those possibilities.
Class selectors
Class selectors might be the most commonly used type of CSS selector you will see around the web. Classes are ideal because they are slightly more specific than element selectors but without the heavy-handedness of IDs. You can read a deep explanation of how the CSS Cascade determines specificity, but the following is an abbreviated illustration focusing specifically (get it?!) on the selector types we’ve looked at so far.
That’s what makes class selectors so popular — they’re only slightly more specific than elements, but keep specificity low enough to be manageable if we need to override the styles in one ruleset with styles in another.
The only difference when writing a class is that we prepend a period (.) in front of the class name instead of the hashtag (#).
/* Selects all <article> elements */
article {
background: #fff;
}
/* Selects any element with class="featured" */
.featured {
background: hsl(35 100% 90%);
border-color: hsl(35 100% 50%);
}
Here’s how our <article> example shapes up when we swap out #featured with .featured.
Same result, better specificity. And, yes, we can absolutely combine different selector types on the same element:
<article id="someID" class="featured">...</article>
Do you see all of the possibilities we have to select an <article>? We can select it by:
- Its element type (
article) - Its ID (
#someID) - Its class (
.featured)
The following articles will give you some clever ideas for using class selectors in CSS.
But we have even more ways to select elements like this, so let’s continue.
Attribute selectors
ID and class selectors technically fall into this attribute selectors category. We call them “attributes” because they are present in the HTML and give more context about the element. All of the following are attributes in HTML:
<!-- ID, Class, Data Attribute -->
<article id="#id" class=".class" data-attribute="attribute">
</article>
<!-- href, Title, Target -->
<a href="https://css-tricks.com" title="Visit CSS-Tricks" target="_blank"></a>
<!-- src, Width, Height, Loading -->
<img src="star.svg" width="250" height="250" loading="laxy" >
<!-- Type, ID, Name, Checked -->
<input type="checkbox" id="consent" name="consent" checked />
<!-- Class, Role, Aria Label -->
<div class="buttons" role="tablist" aria-label="Tab Buttons">
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.
<!-- 👎 No match -->
<a href="https://CSS-Tricks.com">...</a>
<!-- 👍 Match! -->
<a href="https://css-tricks.com">...</a>
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.
<!-- 👍 I match! -->
<a href="https://CSS-Tricks.com">...</a>
<!-- 👍 I match too! -->
<a href="https://css-tricks.com">...</a>
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!