Code of the Day
BeginnerStyling with CSS

Selectors and the Cascade

Selectors target elements; the cascade is the algorithm the browser uses to decide which rule wins when multiple rules apply.

Web FoundationsBeginner8 min read
Recommended first
By the end of this lesson you will be able to:
  • Write type, class, ID, and descendant selectors
  • Explain what "the cascade" means — that multiple rules can apply to the same element and the browser resolves conflicts
  • Understand the three factors in the cascade (origin, specificity, source order)
  • Recognize when to use class selectors vs ID selectors

A CSS rule is useless without a good selector. The selector is what connects a rule to the element it should style. Get selectors wrong and your styles hit the wrong targets — or nothing at all.

Type selectors

The simplest selector is a bare element name. It targets every element of that type on the page:

p {
  color: #333;
}

h1 {
  font-size: 2rem;
}

a {
  text-decoration: underline;
}

Type selectors are broad. p { color: red } turns every paragraph red — useful for defaults, but not fine-grained enough for most real styling.

Class selectors

A class selector starts with a dot and targets every element that has that class in its class attribute:

<p class="intro">This is the lead paragraph.</p>
<p>This is a regular paragraph.</p>
.intro {
  font-size: 1.2rem;
  font-weight: 600;
}

Only the first paragraph is affected. Classes are the workhorse of CSS: they are reusable (add the same class to any element), composable (one element can have multiple classes: class="card featured"), and they convey intent through their names.

ID selectors

An ID selector starts with # and targets the single element with that id attribute:

<header id="site-header">…</header>
#site-header {
  background: #0d1117;
}

IDs are unique per page — no two elements should share the same id. The problem with ID selectors in CSS is their high specificity: they are very hard to override later. The modern convention is to use IDs as hooks for JavaScript (document.getElementById) and classes for all styling. Reach for #id in a stylesheet only when you have a compelling reason.

Descendant and child combinators

Combinators describe a relationship between two selectors.

Descendant (a space between selectors) — matches the right element anywhere inside the left element, at any depth:

article p {
  /* every <p> inside an <article>, no matter how deeply nested */
  line-height: 1.7;
}

Child (>) — matches only direct children, not deeper descendants:

ul > li {
  /* only <li> elements that are immediate children of <ul> */
  list-style: disc;
}

Adjacent sibling (+) — matches an element immediately following another:

h2 + p {
  /* the first <p> that comes directly after an <h2> */
  margin-top: 0;
}

The descendant combinator is the most useful but also the most dangerous — a rule like div p will target every paragraph inside every div on the page. Prefer class selectors and save combinators for genuinely structural relationships.

The cascade

The word "cascade" is the C in CSS. It describes how the browser handles conflicts: what happens when two rules both target the same element and set the same property to different values?

p { color: blue; }
p { color: red; }

Both rules match every <p>. The browser needs a way to pick one. The cascade is the algorithm that does this, and it has three steps — applied in order:

1. Origin and importance

Rules come from different origins: the browser's built-in default styles, any user-defined styles (rare in practice), and your author stylesheet. Author styles override user-agent defaults. The !important flag can reverse this order, but treat it as a last resort — it is covered in the next lesson.

2. Specificity

If two rules come from the same origin, the more specific selector wins. A class selector (.intro) beats a type selector (p); an ID selector (#header) beats both. The full details of specificity scoring are covered in the next lesson — for now, remember: more specific wins.

3. Source order

If two rules tie on origin and specificity, the later rule wins. That is why you write resets and defaults at the top of a stylesheet and overrides further down.

p { color: blue; }
p { color: red; }   /* this wins — later in the file */

Putting it together

Here is a stylesheet fragment that demonstrates all three selector types and the cascade at work:

/* Type selector: all paragraphs are dark grey */
p {
  color: #333;
  line-height: 1.6;
}

/* Class selector: lead paragraphs are slightly larger */
.lead {
  font-size: 1.15rem;
}

/* Descendant: paragraphs inside articles get extra breathing room */
article p {
  margin-bottom: 1.2rem;
}

A <p class="lead"> inside an <article> matches all three rules. The color and line-height come from the type selector. The font-size comes from the class selector (which beats the type selector when both could apply, because classes are more specific). The margin-bottom comes from the descendant combinator rule.

When debugging unexpected styles, open your browser's DevTools and inspect the element. The Styles panel shows every rule that matches, with struck-out declarations indicating which ones were overridden and why. This is the fastest way to understand the cascade in practice.

Where to go next

Next: Specificity — a precise look at how the browser calculates a specificity score for any selector, and what to do when the score system works against you.

Finished reading? Mark it complete to track your progress.

On this page