Home/Learn/HTML, CSS & Responsive/The Cascade, Specificity and Inheritance

The Cascade, Specificity and Inheritance

Intermediate
CSS Fundamentals

When two rules apply, one wins. Knowing exactly how that is decided is the difference between debugging CSS and adding !important until it works.

Overview

The C in CSS is the cascade, and it is the model that decides everything. When more than one rule targets an element, the browser resolves it in a fixed order: origin and importance first, then specificity, then source order. Almost every "my CSS is not applying" moment is a rule you did not know was more specific, and almost every !important is a developer who lost that fight without knowing what it was. Learning the algorithm — it is short — turns CSS from unpredictable into deterministic.

How a Winner Is Chosen

Four steps, in order. Later steps only run if the earlier ones tie.

Origin, then specificity, then order
/* 1. ORIGIN and IMPORTANCE
      author !important > author > user agent (browser defaults)

   2. SPECIFICITY  — counted as (inline, id, class, element)

   3. SOURCE ORDER — the last matching rule wins  */

#main .card p        /* (0,1,1,1) */
.card p.intro        /* (0,0,2,1) */
p                    /* (0,0,0,1) */
.btn                 /* (0,0,1,0) */
style="color:red"    /* (1,0,0,0) — beats every selector */

/* Compare left to right. A single id beats any number of classes:
   (0,1,0,0) > (0,0,20,0) — specificity does not "add up" across
   columns, which surprises people. */

/* Pseudo-classes count as classes; pseudo-elements as elements */
a:hover              /* (0,0,1,1) */
li::marker           /* (0,0,0,2) */

/* These three add nothing at all */
:where(.a, .b)       /* (0,0,0,0) — always zero */
*                    /* (0,0,0,0) */
:is(#x)              /* takes the HIGHEST of its arguments — careful */

Keeping Specificity Low

The strategy that avoids the arms race in the first place.

:where, @layer, and flat selectors
/* An escalating war, one override at a time */
.card p { color: grey }
.page .card p { color: black }
#main .page .card p { color: navy }
#main .page .card p { color: red !important }   /* the surrender */

/* Flat, single-class selectors instead — everything wins by order,
   which is easy to reason about */
.card__text { color: grey }
.card__text--muted { color: silver }

/* :where() zeroes specificity, which is ideal for resets and
   defaults you fully expect to be overridden */
:where(a) { color: var(--link) }        /* any .class beats this */

/* Cascade layers give explicit priority regardless of specificity */
@layer reset, base, components, utilities;
@layer components { #sidebar .nav a { color: blue } }
@layer utilities  { .text-red { color: red } }    /* wins — later layer */

/* Utility frameworks like Tailwind sidestep the problem: every
   class is (0,0,1,0), so order in the stylesheet decides. */

/* !important is legitimate in exactly two places: overriding a
   third-party stylesheet you cannot edit, and utility classes
   that must always win. */

Inheritance

Some properties flow down the tree and some do not. Knowing which saves a lot of repetition.

What inherits, and font: inherit on buttons
/* Inherited — set once on a container */
color  font-family  font-size  font-weight  line-height
letter-spacing  text-align  visibility  cursor  list-style

/* NOT inherited — every element starts fresh */
background  border  padding  margin  width  height  display  position

/* So this styles all descendant text */
.article { font-family: Georgia, serif; line-height: 1.6 }

/* And this does not cascade to children */
.article { border: 1px solid }        /* only the container */

/* Explicit control */
.child { color: inherit }             /* take the parent's value */
.child { all: unset }                 /* reset everything */
button { font: inherit }              /* buttons do NOT inherit font
                                         by default — a classic fix */

/* Custom properties DO inherit, which is what makes theming work */
:root { --text: #111 }
.dark { --text: #eee }                /* everything inside picks it up */

/* Debugging: open DevTools, find the element, and read the Styles
   panel top to bottom. Overridden rules are struck through, and the
   winner is at the top. That panel answers the question faster than
   any amount of guessing. */

Key Points to Remember

  • 1The cascade resolves in order: origin and importance, then specificity, then source order
  • 2Specificity is compared column by column — one id beats any number of classes
  • 3:where() has zero specificity and @layer sets priority explicitly, both of which prevent override wars
  • 4Text properties inherit; box properties do not — and form controls need font: inherit explicitly
  • 5The DevTools Styles panel shows the winner at the top with losers struck through, which answers most CSS questions instantly

Interview Questions

Sign in to ask Aria
1

How does the browser decide which of two conflicting CSS rules applies?

Medium
2

Why does #main .card p beat .card .intro p.text?

Medium
3

What does :where() do to specificity, and when is that useful?

Hard

Ask Aria about The Cascade, Specificity and Inheritance

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…