Skip to main content

🎯 Basic Selectors and Specificity

Selectors are how you tell the browser which elements to style. But when two rules both claim the same element, who wins? That's decided by specificity and the cascade — the single most misunderstood corner of CSS. Master both here and you'll stop fighting your stylesheet and start directing it.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Use the five basic selectors — universal, type, class, ID, and attribute
  • Combine selectors with combinators (descendant, child, sibling, group)
  • Calculate a selector's specificity and predict which rule applies
  • Explain how the cascade breaks ties between equal rules
  • Follow best practices that keep specificity low and CSS maintainable

Estimated Time: 35–45 minutes  •  Difficulty: Beginner–Intermediate

Hands-on: Predict, then resolve, a real specificity conflict three different ways.

In This Lesson

What Selectors Do

A selector is a pattern that matches HTML elements. It's the bridge between your markup and your style rules — telling the browser exactly which elements each declaration block should apply to.

💡 An analogy: Selectors are address labels. You might address a package to "all residents" (the universal selector), "everyone with the job title Editor" (a type selector), "members of the marketing team" (a class), or "employee #4837" (an ID). Same idea, different precision.

The Five Basic Selectors

graph TD A[CSS Selectors] --> B[Universal *] A --> C[Type / element] A --> D[Class .name] A --> E[ID #name] A --> F["Attribute [attr]"]

1. Universal selector *

Matches every element. Most commonly used for a small reset:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

Handy for global resets and quick debugging (* { outline: 1px solid red; } reveals every box on the page), but avoid layering heavy rules on it.

2. Type / element selector

Targets elements by their tag name — perfect for base styles.

p { line-height: 1.6; }
h2 { font-size: 1.5rem; }
a { color: #0066cc; text-decoration: none; }

3. Class selector .name

Targets any element carrying that class. Classes are reusable and the workhorse of modern CSS.

<button class="btn primary">Add to Cart</button>
.btn { padding: 0.5em 1em; border-radius: 4px; cursor: pointer; }
.primary { background: #0066cc; color: white; }

/* Both classes on the SAME element (no space) */
.btn.primary { font-weight: bold; }

⚠️ Space changes everything

.btn.primary (no space) matches one element that has both classes. .btn .primary (with a space) matches a .primary element that is a descendant of a .btn. A single space flips the meaning completely.

4. ID selector #name

Targets the one element with that id. IDs must be unique per page and carry high specificity.

#main-header {
  background: #333;
  color: white;
  padding: 1rem;
}

💡 Prefer classes for styling

Most modern methodologies avoid IDs for styling because their high specificity is hard to override later. Reserve IDs for JavaScript hooks and in-page anchor links (page.html#section); style with classes.

5. Attribute selector [attr]

Targets elements by an attribute's presence or value, with several matching operators:

[type="text"]   { border: 1px solid #ccc; }  /* exact match */
[class*="btn"]  { cursor: pointer; }          /* contains "btn" */
[href^="https"] { color: green; }             /* starts with */
[src$=".jpg"]   { border: 2px solid #ddd; }   /* ends with */
[data-status="active"] { background: #e6ffe6; } /* data-attribute */
OperatorMatches when the attribute…
[attr]exists, any value
[attr="v"]equals exactly v
[attr*="v"]contains v anywhere
[attr^="v"]starts with v
[attr$="v"]ends with v

Combining Selectors

Combinators join simple selectors to describe relationships in the document tree.

CombinatorExampleSelects
Descendant (space).article pevery p anywhere inside .article
Child (>).nav > lionly li that are direct children of .nav
Adjacent sibling (+)h2 + pthe p immediately after an h2
General sibling (~)h2 ~ pevery p after an h2 (same parent)
Group (,)h1, h2, h3all three heading types at once
How combinators walk the DOM tree A container holds a section; the section holds an h2 followed by two paragraphs, illustrating descendant, child, and adjacent-sibling relationships. div.container section h2 p p section > p → both paragraphs (direct children) h2 + p → only the first paragraph (immediately after h2)
Figure 1 — h2 + p matches only the paragraph directly following the heading, while section > p matches both direct-child paragraphs.

Specificity

When more than one rule targets the same element and they set the same property, specificity decides the winner. It's a scoring system: the more specific selector applies its style.

📖 The specificity ranking (low → high)

1. Type selectors & pseudo-elements (h1, ::before)
2. Classes, attribute selectors & pseudo-classes (.card, [type="text"], :hover)
3. ID selectors (#header)
4. Inline styles (style="…")
5. !important — overrides the normal contest entirely

⚠️ !important is a last resort

It wins by breaking the rules rather than playing them, and it tends to start an "arms race" where the only way to override one !important is another. Reach for better selectors or source order first.

Calculating Specificity

Specificity is usually written as three numbers, (ID, class, type) — sometimes with a leading slot for inline styles. Count the pieces in your selector:

  • ID column: number of #id selectors
  • Class column: number of classes, attribute selectors, and pseudo-classes
  • Type column: number of element/type selectors and pseudo-elements
SelectorSpecificity (ID-Class-Type)Why
p0-0-1one type
p.intro0-1-1one type + one class
.container p.intro0-2-1two classes + one type
#header .nav1-1-0one ID + one class
#special.text1-1-0one ID + one class
inline style="…"1-0-0-0inline styles outrank selectors

Compare columns left to right. A single ID (1-0-0) beats any number of classes (0-99-0) — the columns don't "carry" like ordinary digits.

A worked conflict

<div id="container">
  <p class="text">First</p>
  <p class="text highlight">Second</p>
  <p id="special" class="text">Third</p>
</div>
p               { color: black; }  /* 0-0-1 */
.text           { color: blue;  }  /* 0-1-0 */
.text.highlight { color: orange;}  /* 0-2-0 */
#container p    { color: green; }  /* 1-0-1 */
#special.text   { color: red;   }  /* 1-1-0 */

Resulting colors

First → green (#container p, 1-0-1 beats .text's 0-1-0).
Second → green as well — #container p (1-0-1) still outranks .text.highlight (0-2-0), because one ID beats any number of classes.
Third → red (#special.text, 1-1-0 is the highest here).

The Cascade

When two rules have equal specificity, the one declared last wins. That "last one wins" behavior is what puts the "cascading" in Cascading Style Sheets.

.button { background: blue; }
/* Same specificity, declared later — this one wins */
.button { background: green; }

The full order the browser uses to resolve any conflict:

  1. Origin & importance — browser defaults, then your styles, with !important flipping the priority
  2. Specificity — the scoring above
  3. Source order — last declaration wins on a tie

✅ Use the cascade on purpose

Put general, low-specificity rules first and specific variations later. When you let source order do the work, you rarely need to escalate specificity or reach for !important.

Best Practices

✅ Do

  • Style with classes — the sweet spot of reuse and low specificity
  • Keep selectors flat; a shallow .card-title beats a deep .card > div > h2
  • Name by purpose (.btn-primary), not appearance (.btn-blue)
  • Aim for a consistent specificity ceiling across your stylesheet

⚠️ Avoid

  • Styling with IDs — they spike specificity and are hard to override
  • !important as a routine fix — it signals a structural problem
  • Deep descendant chains that make later overrides a battle

Methodologies like BEM exist precisely to keep specificity flat by using descriptive single classes:

.card {}          /* Block */
.card__title {}   /* Element */
.card--featured {}/* Modifier */

Hands-on Exercise

🏋️ Resolve a Specificity Conflict Three Ways

Objective: Deliberately create a conflict, then fix it three different ways.

Instructions:

  1. Make a paragraph with a class: <p class="note">Read me</p>.
  2. Write two rules that fight: p { color: gray; } and, earlier in the file, .note { color: crimson; }. Predict the color before testing.
  3. Now suppose a later rule p { color: black; } appears. Get the text crimson again three ways: (a) raise the class rule's specificity, (b) move it after the p rule, (c) add !important (for demonstration only).
  4. Note the specificity value beside each selector in a comment.
💡 Hint

The class rule .note (0-1-0) already beats p (0-0-1) on specificity regardless of order. The only way p could win is if it's more specific or comes later at equal specificity — which is exactly the situation to engineer and then fix.

✅ Sample solution
/* (a) raise specificity — 0-2-0 beats a bare p */
p.note { color: crimson; }

/* (b) source order — same specificity, declared last wins */
p { color: black; }
.note { color: crimson; }

/* (c) last resort — avoid in real code */
.note { color: crimson !important; }

🎯 Quick Quiz

Question 1: What does .btn.primary (no space) match?

Question 2: Between #container p and .text.highlight, which wins?

Question 3: Two rules have equal specificity. Which one applies?

Summary & Quiz

🎉 Key Takeaways

  • The five basic selectors — universal, type, class, ID, attribute — target elements with rising precision.
  • Combinators (space, >, +, ~, ,) describe relationships between elements.
  • Specificity is scored as (ID, class, type); one ID beats any number of classes.
  • On a specificity tie, the cascade gives the win to the last declaration.
  • Prefer classes, keep selectors flat, and treat !important as a last resort.

📚 Further Reading

🚀 What's Next?

With selectors under control, we turn to what they most often style: text. Next up is typography — fonts, sizing, spacing, and the properties that make text readable.

🎉 Nice work!

You can now aim CSS precisely and predict which rule wins. That's a superpower.