🎯 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
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 */
| Operator | Matches 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.
| Combinator | Example | Selects |
|---|---|---|
| Descendant (space) | .article p | every p anywhere inside .article |
Child (>) | .nav > li | only li that are direct children of .nav |
Adjacent sibling (+) | h2 + p | the p immediately after an h2 |
General sibling (~) | h2 ~ p | every p after an h2 (same parent) |
Group (,) | h1, h2, h3 | all three heading types at once |
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
#idselectors - Class column: number of classes, attribute selectors, and pseudo-classes
- Type column: number of element/type selectors and pseudo-elements
| Selector | Specificity (ID-Class-Type) | Why |
|---|---|---|
p | 0-0-1 | one type |
p.intro | 0-1-1 | one type + one class |
.container p.intro | 0-2-1 | two classes + one type |
#header .nav | 1-1-0 | one ID + one class |
#special.text | 1-1-0 | one ID + one class |
inline style="…" | 1-0-0-0 | inline 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:
- Origin & importance — browser defaults, then your styles, with
!importantflipping the priority - Specificity — the scoring above
- 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-titlebeats 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
!importantas 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:
- Make a paragraph with a class:
<p class="note">Read me</p>. - Write two rules that fight:
p { color: gray; }and, earlier in the file,.note { color: crimson; }. Predict the color before testing. - 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 theprule, (c) add!important(for demonstration only). - 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
!importantas 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.