Selectors & The Cascade: Precision Styling
In the previous chapter, you learned how to write CSS rules. This chapter answers the next important question: which elements does a rule affect, and what happens when several rules try to style the same element?
That is the job of two CSS systems:
- Selectors decide what elements a rule targets.
- The cascade decides which declaration wins when rules conflict.
If HTML is the structure of a page, selectors are how CSS points at parts of that structure. The cascade is the browser's conflict-resolution algorithm.
1. Mental Model: CSS Is a Matching System
A CSS rule has two main parts:
selector {
property: value;
}
The browser reads the selector and checks every element in the document. If an element matches, the declarations inside the rule apply to that element.
p {
color: #334155;
}
This does not mean "find one paragraph." It means "every <p> element matches this selector."
The selector is not executed like JavaScript. It is matched against the page tree.
2. Core Selectors
These are the selectors you will use constantly.
A. Element or Type Selector
An element selector targets every element with a given tag name.
p {
color: #333;
}
<p>This paragraph is affected.</p>
<p>This paragraph is affected too.</p>
Use element selectors for broad defaults: headings, paragraphs, lists, tables, forms, and page-wide typography.
B. Class Selector
A class selector targets elements with a matching class attribute.
.highlight {
background-color: yellow;
}
<p class="highlight">I am highlighted!</p>
<span class="highlight">Me too!</span>
Classes are reusable. They are the most common way to style components because the same class can appear on many elements.
C. ID Selector
An ID selector targets one element with a matching id.
#main-navigation {
background: #000;
}
<nav id="main-navigation">...</nav>
IDs should be unique on a page. They also have high specificity, so they are harder to override than classes. In modern CSS, prefer classes for styling and reserve IDs for anchors, labels, and JavaScript hooks when necessary.
D. Attribute Selector
An attribute selector targets elements based on attributes or attribute values.
input[required] {
border-color: red;
}
a[target="_blank"] {
text-decoration-style: dotted;
}
Attribute selectors are useful when the HTML already carries meaningful state, such as required, disabled, aria-current, type, or target.
E. Universal Selector
The universal selector * matches every element.
* {
box-sizing: border-box;
}
It is commonly used for resets. Avoid using it for heavy visual styling because it touches everything.
3. Selector Cheat Sheet
| Selector | Example | Meaning |
|---|---|---|
| Type | button | Every <button> |
| Class | .card | Every element with class="card" |
| ID | #hero | The element with id="hero" |
| Attribute | [disabled] | Every element with a disabled attribute |
| Attribute value | input[type="email"] | Email inputs |
| Universal | * | Every element |
4. Combining Selectors
Selectors become powerful when you combine them.
A. Grouping Selectors
Use commas to apply the same declarations to multiple selectors.
h1,
h2,
h3,
.main-title {
font-family: Helvetica, Arial, sans-serif;
}
The comma means "this selector OR that selector."
B. Compound Selectors
A compound selector has multiple requirements on the same element.
button.primary {
background: blue;
}
This means: target a <button> that also has class primary.
<button class="primary">Matches</button>
<a class="primary">Does not match</a>
C. Descendant Combinator
A space means "inside at any depth."
article p {
line-height: 1.8;
}
This targets paragraphs anywhere inside an <article>, including deeply nested paragraphs.
D. Direct Child Combinator
The > combinator means "direct child only."
ul > li {
border-bottom: 1px solid #ccc;
}
This targets only <li> elements whose immediate parent is the <ul>.
E. Adjacent Sibling Combinator
The + combinator targets the next sibling only.
h2 + p {
margin-top: 0;
}
This is useful for styling the first paragraph immediately after a heading.
F. General Sibling Combinator
The ~ combinator targets later siblings that share the same parent.
h2 ~ p {
color: #475569;
}
This means: paragraphs that come after an h2 under the same parent.
5. Pseudo-Classes: Selecting State and Position
Pseudo-classes target elements in a certain state or position.
button:hover {
background-color: darkblue;
}
input:focus {
outline: 3px solid #93c5fd;
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
Common pseudo-classes:
:hovermatches when the pointer is over an element.:focusmatches when an element receives keyboard or pointer focus.:activematches while an element is being activated.:checkedmatches selected checkboxes and radio buttons.:disabledmatches disabled form controls.:first-childmatches an element that is the first child of its parent.:last-childmatches an element that is the last child of its parent.:nth-child(odd)and:nth-child(even)are common for striped lists and tables.
.menu-item:first-child {
border-top-left-radius: 12px;
}
.menu-item:last-child {
border-bottom-left-radius: 12px;
}
6. Pseudo-Elements: Styling Parts That Are Not Real Elements
Pseudo-elements let CSS style a specific part of an element.
p::first-line {
font-weight: bold;
}
.note::before {
content: "Note: ";
font-weight: bold;
}
Common pseudo-elements:
::beforeinserts generated content before an element's content.::afterinserts generated content after an element's content.::first-linestyles the first rendered line of text.::first-letterstyles the first letter.::placeholderstyles form placeholder text.
Generated content should be decorative or supplemental. Do not use ::before or ::after for essential content that screen readers must understand.
7. The Cascade: How CSS Resolves Conflicts
When multiple declarations target the same property on the same element, CSS must pick one value.
The cascade compares rules in this order:
- Importance:
!importantdeclarations beat normal declarations. - Origin: browser styles, user styles, and author styles have different priority.
- Specificity: more specific selectors beat less specific selectors.
- Source order: if specificity is tied, the rule written later wins.
For day-to-day CSS, the most important parts are specificity and source order.
p {
color: red;
}
.intro {
color: blue;
}
<p class="intro">This text becomes blue.</p>
The .intro selector wins because a class selector is more specific than a type selector.
8. Specificity: The Point System
Specificity is often explained as a score with four columns:
(inline, IDs, classes/attributes/pseudo-classes, elements/pseudo-elements)
Examples:
| Selector | Specificity | Why |
|---|---|---|
h1 | 0-0-0-1 | One element |
.title | 0-0-1-0 | One class |
h1.title | 0-0-1-1 | One class and one element |
#hero .title | 0-1-1-0 | One ID and one class |
article.card p | 0-0-1-2 | One class and two elements |
style="color:red" | 1-0-0-0 | Inline style |
Scenario
h1 {
color: red;
}
.title {
color: blue;
}
#hero .title {
color: green;
}
<section id="hero">
<h1 class="title">Welcome</h1>
</section>
The heading becomes green. #hero .title has an ID and a class, so it beats both .title and h1.
Important detail: specificity is not normal decimal math. 0-0-10-0 does not beat 0-1-0-0. Ten classes do not beat one ID.
9. Source Order: Later Wins When Specificity Ties
If two rules have the same specificity, the one that appears later in the stylesheet wins.
.button {
background: gray;
}
.button {
background: black;
}
The button becomes black because the second .button rule appears later.
This is why CSS files are usually organized from broad styles to specific styles:
/* 1. Base */
body { font-family: system-ui; }
/* 2. Layout */
.container { max-width: 960px; }
/* 3. Components */
.card { padding: 1rem; }
/* 4. Utilities */
.text-center { text-align: center; }
10. Inheritance: What Flows Down
Some properties automatically flow from parent elements to child elements. This is called inheritance.
Common inherited properties:
colorfont-familyfont-sizeline-heighttext-alignvisibility
Common non-inherited properties:
marginpaddingborderwidthheightbackground
body {
color: #1f2937;
font-family: Arial, sans-serif;
}
Most text inside the page inherits that color and font unless a more specific rule overrides it.
You can explicitly control inheritance with these values:
a {
color: inherit;
}
.component {
all: unset;
}
Use inherit when you want a child to use the parent value. Use unset carefully because it resets many assumptions.
11. The !important Escape Hatch
!important overrides normal cascade behavior.
.warning {
color: orange !important;
}
It can be useful when overriding third-party CSS, but it should not be your normal solution. Too many !important declarations turn your stylesheet into a fight where every future rule needs to be even stronger.
Prefer these fixes first:
- Use a better selector.
- Move the rule later in the stylesheet.
- Reduce overly specific selectors elsewhere.
- Add a clearer component class.
12. Practical Selector Patterns
A. Component Root + Element Classes
.course-card {
border: 1px solid #e2e8f0;
border-radius: 16px;
}
.course-card__title {
font-size: 1.25rem;
}
<article class="course-card">
<h2 class="course-card__title">CSS Basics</h2>
</article>
This avoids selectors like .page main section article h2, which are fragile because they depend too much on document structure.
B. State Classes
.tab {
color: #64748b;
}
.tab.is-active {
color: #0f172a;
border-bottom: 2px solid currentColor;
}
State classes make UI state readable in both HTML and CSS.
C. Attribute State
a[aria-current="page"] {
font-weight: 700;
}
This connects accessibility state and visual state. The same attribute that helps assistive technology can also style the current navigation item.
13. Debugging Cascade Problems
When a CSS rule does not work, ask these questions:
- Does the selector actually match the element?
- Is the property crossed out in DevTools?
- Is another rule more specific?
- Is another rule with equal specificity written later?
- Is the property inherited from a parent?
- Is an inline style or
!importantinvolved?
Browser DevTools are the best way to debug CSS. Inspect the element and look at the Styles panel. The browser will show which rules match, which declarations are overridden, and where each rule came from.
14. Practical Pro Tips
- Use classes for most styling. They are reusable and easier to override than IDs.
- Avoid deeply nested selectors.
.card h2is usually fine;.page main section article div h2is brittle. - Let inheritance do work. Set typography defaults on
bodyor layout containers. - Keep specificity low. Low-specificity CSS is easier to maintain.
- Use source order intentionally. Put base styles first, components next, utilities last.
- Be careful with
!important. It solves one conflict by making future conflicts harder. - Prefer semantic state. Use
[aria-current="page"],[disabled], and.is-activewhen they describe real state.
15. Mini Practice
Given this HTML:
<article id="featured" class="card">
<h2 class="title">Selectors</h2>
<p class="summary">Learn how CSS finds elements.</p>
</article>
Which color wins for the heading?
h2 {
color: red;
}
.title {
color: blue;
}
#featured .title {
color: green;
}
article h2 {
color: purple;
}
Answer: green. The selector #featured .title has the highest specificity because it contains an ID and a class.
Now try writing selectors for these goals:
- Style only paragraphs inside
.card. - Style only direct list items inside
.menu. - Style the current navigation link using
aria-current="page". - Style every disabled button.
- Style the first paragraph immediately after an
h2.
Possible answers:
.card p {}
.menu > li {}
a[aria-current="page"] {}
button:disabled {}
h2 + p {}