Skip to content
AgileLogics
7 min read

The Cascade, Specificity, and Inheritance

Prerequisites: Combinators

When several declarations set the same property on the same element, the cascade picks one. For everyday CSS, three things decide it, in this order:

  1. Importance and origin — !important declarations beat normal ones; author styles (yours) beat the browser's defaults.
  2. Specificity — the more specific selector wins.
  3. Order — if specificity is equal, the declaration that appears last wins.

(Level 3 adds cascade layers and scoping to the picture.)

Specificity

Count the selector's parts in three columns:

Column Counts
A id selectors (#main)
B classes, attribute selectors, pseudo-classes (.card, [type], :hover)
C type selectors and pseudo-elements (p, ::before)

Compare column A first, then B, then C — like version numbers. * and combinators count nothing.

Selector Specificity
p 0,0,1
nav a 0,0,2
.card 0,1,0
.card p 0,1,1
a:hover 0,1,1
#main .card 1,1,0
style="..." attribute beats any selector

One id beats any number of classes: #main (1,0,0) wins over .a.b.c.d.e.f (0,6,0).

<style>
  #intro { color: purple; }
  .text.lead { color: green; }
  .lead { color: blue; }
  p { color: red; }
  p { color: orange; }
</style>
<p id="intro" class="text lead">A</p>
<p class="text lead">B</p>
<p class="lead">C</p>
<p>D</p>
<script>
  for (const p of document.querySelectorAll("p")) {
    console.log(p.textContent, getComputedStyle(p).color);
  }
</script>
A rgb(128, 0, 128)
B rgb(0, 128, 0)
C rgb(0, 0, 255)
D rgb(255, 165, 0)
  • A is purple: the id wins over everything.
  • B is green: two classes beat one.
  • C is blue: a class beats a type selector.
  • D is orange: two equal p rules — the later one wins.

!important

color: red !important; beats all normal declarations, whatever their specificity. It's a sledgehammer: the only way to override it is another !important with higher specificity. Reserve it for utility classes and for overriding third-party CSS you can't change.

Inheritance

Some properties pass from parent to child automatically — mostly text properties: color, font-*, line-height, letter-spacing, text-align, visibility, cursor, list-style. Box properties — margin, padding, border, background, width — are not inherited.

<style>
  .box { color: teal; border: 2px solid teal; font-size: 20px; }
</style>
<div class="box"><p>Inside</p></div>
<script>
  const p = getComputedStyle(document.querySelector(".box p"));
  console.log(p.color, p.fontSize, p.borderTopWidth, p.borderTopStyle);
</script>
rgb(0, 128, 128) 20px 0px none

The paragraph inherited the color and font size but not the border.

An inherited value always loses to a rule that targets the element directly, however weak its selector — p { color: red } beats a color inherited from #main.

Controlling inheritance

Every property accepts these keywords:

  • inherit — take the parent's value.
  • initial — the property's initial value from the specification.
  • unset — inherit for inherited properties, otherwise initial.
  • revert — back to the browser's default style.
button {
  font: inherit;
  color: inherit;
}

Form controls don't inherit fonts by default — this common reset makes them match the surrounding text.

Practice

Which color does the link get, and why?

<style>
  #nav a { color: white; }
  .menu-link { color: yellow; }
  a.menu-link.active { color: orange; }
</style>
<nav id="nav"><a class="menu-link active" href="/">Home</a></nav>
Show solution

White. #nav a is 1,0,1; a.menu-link.active is 0,2,1; .menu-link is 0,1,0. The id makes #nav a the most specific, even though the other rules come later. This is why ids in selectors cause trouble: to make the active link orange, you'd have to write #nav a.active — replace #nav a with .nav a instead.