Skip to main content

Command Palette

Search for a command to run...

CSS Selectors 101: Targeting Elements with Precision

The thing that determines whether your styles actually land

Updated
9 min read

In my early days of learning CSS, I spent a genuinely embarrassing amount of time adding styles that did nothing. I'd write color: red, save the file, refresh the browser, and the text would stay black. I'd write it again, in a slightly different place in my stylesheet. Still black.

The problem wasn't the CSS property. It was the selector.

I was trying to style the wrong thing, or targeting elements that didn't exist the way I thought they did. CSS selectors are how you tell the browser which elements your styles apply to — and getting them wrong means your styles go nowhere.

Once I understood selectors properly, CSS clicked. Not all of it, but enough. The styles started landing. The page started looking like what I intended.

This blog is the thing I wish I'd read before I wasted those hours.

Why Selectors Exist

CSS stands for Cascading Style Sheets. A stylesheet contains rules. A rule looks like this:

p {
  color: blue;
  font-size: 16px;
}

That rule has two parts: the selector (p) and the declarations (color: blue; font-size: 16px;).

The selector is the targeting mechanism. It answers the question: which elements on the page should receive these styles?

Without a selector, CSS has no idea where to apply your rules. A declaration without a selector is like a letter without an address — the content is there, but it has no idea where to go.

Think of selectors as different levels of addressing. If you're sending an announcement to everyone in a building, that's one kind of targeting. If you're sending a note specifically to the person in flat 3B, that's different — more specific, more precise. CSS selectors work the same way. Some target broadly. Some target exactly one element.

Let's go through them in order of specificity, starting with the broadest.

The Element Selector: Everyone with That Name

The element selector targets every instance of a particular HTML tag on the page.

p {
  color: #333;
  line-height: 1.6;
}

This applies color: #333 and line-height: 1.6 to every <p> element on the page. Every paragraph. All of them.

h1 {
  font-size: 2rem;
}

a {
  color: blue;
}

button {
  cursor: pointer;
}

Element selectors are the broadest kind. They're good for setting baseline styles — things you want to be consistent everywhere that tag appears. Font sizes for headings, base text color for paragraphs, default link colors.

The downside is exactly that broadness. If you want one paragraph to look different from all the others, the element selector can't help you. That's where class selectors come in.

The Class Selector: Grouped by Role

A class selector targets elements based on the class attribute in their HTML.

In your HTML:

<p class="intro">Welcome to my blog. This is the opening paragraph.</p>
<p>This is a regular paragraph with no special class.</p>
<p class="intro">Another intro-styled paragraph on a different page section.</p>

In your CSS:

.intro {
  font-size: 1.2rem;
  font-weight: 500;
  color: #1a1a1a;
}

The . before the name is what tells CSS "this is a class selector." Only elements with class="intro" receive these styles. The regular <p> in the middle is untouched.

Class selectors are the workhorse of CSS. They're reusable — the same class can be applied to as many elements as you want, and they'll all share those styles. They're flexible — you can add multiple classes to one element:

<div class="card featured">...</div>
.card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
}

.featured {
  border-color: #0070f3;
  background: #f0f7ff;
}

That <div> gets styles from both .card and .featured. Classes compose. You build small, focused style rules and combine them where needed.

This is how most modern CSS is written. Element selectors for baselines. Class selectors for components and variations.

The ID Selector: The Unique Target

An ID selector targets one specific element — the element with that exact id attribute.

In your HTML:

<header id="main-header">
  <nav>...</nav>
</header>

In your CSS:

#main-header {
  background: #0a0a0a;
  padding: 0 40px;
  height: 64px;
}

The # before the name marks it as an ID selector. Only the element with id="main-header" receives these styles.

The key difference between class and ID: an ID must be unique on the page. You can give the same class to dozens of elements. An ID should appear exactly once. It's an identifier — like a Social Security number for your HTML element. Every element can have one, but no two elements should share the same one.

Because of this uniqueness, ID selectors are typically used for major layout elements — the page header, the main content wrapper, the footer. Things that appear once.

Here's a comparison to make the three selectors concrete:

<!-- Element selector targets this and every other <p> -->
<p>Regular paragraph</p>

<!-- Class selector targets this and every other .highlight element -->
<p class="highlight">Highlighted paragraph</p>

<!-- ID selector targets ONLY this element -->
<p id="intro-text">The unique opening paragraph</p>
/* Targets all <p> elements */
p { color: #333; }

/* Targets all elements with class="highlight" */
.highlight { background: yellow; }

/* Targets only the element with id="intro-text" */
#intro-text { font-size: 1.3rem; }

Group Selectors: One Rule for Many

Sometimes you want the same styles applied to multiple different selectors. You could write separate rules:

h1 { font-family: 'Georgia', serif; }
h2 { font-family: 'Georgia', serif; }
h3 { font-family: 'Georgia', serif; }

Or you can group them with a comma:

h1, h2, h3 {
  font-family: 'Georgia', serif;
}

Same result. The comma means "apply this rule to each of these selectors." It works with any combination — element selectors, class selectors, ID selectors, mixed:

h1, .section-title, #page-heading {
  font-weight: 700;
  letter-spacing: -0.02em;
}

Group selectors are purely a convenience. They reduce repetition. If you find yourself writing the same declarations across multiple separate rules, group them.

Descendant Selectors: Targeting by Context

Sometimes you don't just want to target an element — you want to target an element only when it's inside another specific element.

That's what descendant selectors do.

article p {
  line-height: 1.8;
  margin-bottom: 1rem;
}

The space between article and p is the descendant combinator. This rule applies to <p> elements that are inside an <article> element. A <p> that's outside of an <article> is not affected.

Here's a concrete scenario. You have a navigation bar and a main content area. Both contain links. But you want navigation links to look different from content links:

<nav>
  <a href="/">Home</a>
  <a href="/about">About</a>
</nav>

<main>
  <p>Read more about this in <a href="/docs">the documentation</a>.</p>
</main>
/* All links - base style */
a {
  text-decoration: none;
}

/* Links inside nav only */
nav a {
  color: white;
  font-weight: 600;
  padding: 8px 16px;
}

/* Links inside main only */
main a {
  color: #0070f3;
  text-decoration: underline;
}

The two nav a and main a rules apply to completely different sets of links, even though both target <a> elements. The context — where the link lives — determines which style it gets.

Descendant selectors don't have to be just one level deep. nav ul li a would target links that are list items inside an unordered list inside a nav. The descendant relationship can be any number of levels deep.

Selector Priority: When Rules Conflict

Here's something that tripped me up constantly when starting out. What happens when two CSS rules target the same element with conflicting styles?

p {
  color: black;
}

.intro {
  color: blue;
}
<p class="intro">Which color is this?</p>

That paragraph has both a <p> tag and a class of intro. Both rules apply to it. Both set a color. Which one wins?

The answer is blue — the class selector wins. This is called specificity, and the rule is straightforward at this level:

ID selectors beat class selectors. Class selectors beat element selectors.

Think of it as a ranking:

ID selector      → highest priority
class selectormedium priority
element selectorlowest priority

More specific selectors override less specific ones, regardless of the order in the stylesheet. If you write an element selector after a class selector targeting the same property, the class selector still wins.

/* Even though this comes second, the class selector above still wins */
p {
  color: black;    /* loses to .intro */
}

.intro {
  color: blue;     /* wins over p */
}

And an ID selector would beat both:

p { color: black; }       /* loses */
.intro { color: blue; }   /* also loses */
#hero-text { color: red; } /* wins */
<p class="intro" id="hero-text">Red text</p>

This is a very high-level view of specificity — there's more to it when you get into combined selectors and the !important declaration. But for the selectors covered in this blog, this ranking is all you need.

Putting It Together

Let's look at a small real example that uses all the selector types from this blog:

<header id="site-header">
  <nav>
    <a href="/">Home</a>
    <a href="/blog" class="active">Blog</a>
    <a href="/about">About</a>
  </nav>
</header>

<main>
  <h1>Recent Posts</h1>
  <article class="post">
    <h2>Post Title</h2>
    <p class="excerpt">A short preview of the post content.</p>
  </article>
  <article class="post">
    <h2>Another Post</h2>
    <p class="excerpt">Another short preview.</p>
  </article>
</main>
/* Element selector - all h1s */
h1 {
  font-size: 2rem;
  margin-bottom: 1.5rem;
}

/* ID selector - only the site header */
#site-header {
  background: #0a0a0a;
  padding: 16px 40px;
}

/* Class selector - all nav links marked active */
.active {
  color: #0070f3;
  font-weight: 600;
}

/* Group selector - h1 and h2 together */
h1, h2 {
  font-family: 'Georgia', serif;
}

/* Descendant selector - only links inside the nav */
nav a {
  color: white;
  text-decoration: none;
  margin-right: 24px;
}

/* Class selector - excerpt paragraphs */
.excerpt {
  color: #666;
  font-size: 0.95rem;
}

Seven CSS rules. Every type of selector we covered. Each one targeting a specific group of elements for a specific reason.

Selectors Are the Foundation

Everything you do in CSS starts with a selector. Color, spacing, typography, layout — none of it applies until you've correctly targeted the elements you want to style.

Getting selectors right is the difference between CSS that works and CSS that sits in your file doing nothing. Once you're comfortable with element, class, ID, group, and descendant selectors, you have the foundation to style basically anything.

The next step after this is pseudo-classes (a:hover, li:first-child) and pseudo-elements (::before, ::after) — but those build directly on top of what we covered here. Selectors first. Everything else follows.


A quick way to practice: open your browser DevTools, click on any element on any webpage, and look at the "Styles" panel on the right. You'll see exactly which CSS rules are applying to that element, and which selectors targeted it. That panel is the best way to understand how selectors work in practice — someone else's CSS, live, showing you the targeting in real time.