Selectors & The Cascade: Precision Styling

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."

p { color: blue; }<p>Matches</p><h1>No</h1><p>Matches</p>

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

SelectorExampleMeaning
TypebuttonEvery <button>
Class.cardEvery element with class="card"
ID#heroThe element with id="hero"
Attribute[disabled]Every element with a disabled attribute
Attribute valueinput[type="email"]Email inputs
Universal*Every element
button.card#hero[hidden]Tag nameReusable hookUnique hookAttribute hookSelectors are different ways to point at elements.

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>.

article p<p>direct</p><section><p>nested</p>article > p<p>direct</p><section><p>nested</p>Descendant: both paragraphs match.Child: only the direct paragraph matches.

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:

  • :hover matches when the pointer is over an element.
  • :focus matches when an element receives keyboard or pointer focus.
  • :active matches while an element is being activated.
  • :checked matches selected checkboxes and radio buttons.
  • :disabled matches disabled form controls.
  • :first-child matches an element that is the first child of its parent.
  • :last-child matches 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:

  • ::before inserts generated content before an element's content.
  • ::after inserts generated content after an element's content.
  • ::first-line styles the first rendered line of text.
  • ::first-letter styles the first letter.
  • ::placeholder styles 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:

  1. Importance: !important declarations beat normal declarations.
  2. Origin: browser styles, user styles, and author styles have different priority.
  3. Specificity: more specific selectors beat less specific selectors.
  4. 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.

1. Importance: is one declaration !important?2. Origin: browser, user, or author CSS?3. Specificity: which selector scores higher?4. Source order: which rule comes later?

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:

SelectorSpecificityWhy
h10-0-0-1One element
.title0-0-1-0One class
h1.title0-0-1-1One class and one element
#hero .title0-1-1-0One ID and one class
article.card p0-0-1-2One class and two elements
style="color:red"1-0-0-0Inline 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.

h1 = 0-0-0-1.title = 0-0-1-0#hero .title = 0-1-1-0Winning value:color: green

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:

  • color
  • font-family
  • font-size
  • line-height
  • text-align
  • visibility

Common non-inherited properties:

  • margin
  • padding
  • border
  • width
  • height
  • background
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.

body { color: slate; }<main><p>Text inherits body color</p>color flows down

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:

  1. Does the selector actually match the element?
  2. Is the property crossed out in DevTools?
  3. Is another rule more specific?
  4. Is another rule with equal specificity written later?
  5. Is the property inherited from a parent?
  6. Is an inline style or !important involved?

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.

1. Selector matches?2. Declaration crossed out?3. Higher specificity elsewhere?4. Same specificity but later source order?

14. Practical Pro Tips

  1. Use classes for most styling. They are reusable and easier to override than IDs.
  2. Avoid deeply nested selectors. .card h2 is usually fine; .page main section article div h2 is brittle.
  3. Let inheritance do work. Set typography defaults on body or layout containers.
  4. Keep specificity low. Low-specificity CSS is easier to maintain.
  5. Use source order intentionally. Put base styles first, components next, utilities last.
  6. Be careful with !important. It solves one conflict by making future conflicts harder.
  7. Prefer semantic state. Use [aria-current="page"], [disabled], and .is-active when 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:

  1. Style only paragraphs inside .card.
  2. Style only direct list items inside .menu.
  3. Style the current navigation link using aria-current="page".
  4. Style every disabled button.
  5. Style the first paragraph immediately after an h2.

Possible answers:

.card p {}
.menu > li {}
a[aria-current="page"] {}
button:disabled {}
h2 + p {}