The cascade and specificity: how conflicting CSS rules actually resolve
Part 1 mentioned that later rules override earlier ones. That's only part of the real story — CSS resolves conflicts through a genuine, defined algorithm, and understanding it is what turns "why isn't my style applying" from a mystery into something diagnosable in seconds.
A real conflict, and why one rule wins
p {
color: #555;
}
.intro {
color: #3d2b1f;
}<p class="intro">Small-batch coffee, roasted weekly.</p>Both rules match this real paragraph. The class selector wins, and the text renders in #3d2b1f — not because it comes second in the file, but because a class selector is genuinely more specific than an element selector, and specificity is checked before source order ever comes into play.
Specificity: a real, calculable score
Inline style → 1,0,0,0 (highest of the four)
ID selector → 0,1,0,0
Class, attribute,
pseudo-class selector → 0,0,1,0
Element, pseudo-element → 0,0,0,1#site-header .nav-link { } /* 0,1,1,0 */
.product-card h3 { } /* 0,0,1,1 */
nav a:hover { } /* 0,0,2,1 */Each selector gets a real, calculable specificity score across four categories, compared left to right — an ID selector beats any number of class selectors, and a class selector beats any number of element selectors, regardless of how many of the lower category are stacked together. #site-header .nav-link (one ID, one class) beats .product-card h3 (one class, one element) — not because it's more complex-looking, but because its highest category (an ID) outranks the other rule's highest category (a class).
Source order: the real tiebreaker, not the first rule
.price { color: #555; }
.price { color: #ff8a3d; }Source order only decides the outcome when two real rules have identical specificity — here, both selectors are single classes, so the one that appears later in the file (or later in a later-linked stylesheet) wins. This is genuinely why "just move my CSS rule further down the file" is a common real fix — but only actually works when specificity is already tied; it does nothing against a rule with a genuinely higher specificity score written earlier.
!important: a real escape hatch, and a real trap
.price {
color: #ff8a3d !important;
}!important overrides normal specificity entirely — a declaration marked this way wins regardless of the selector's own specificity score, with only another !important declaration able to override it. It's a real, working tool, but a genuinely dangerous habit: once one real rule uses !important, overriding it later requires either another !important (compounding the problem) or increasing specificity to a point that also affects other, unrelated selectors — a real, common source of stylesheets that become progressively harder to reason about over time.
Reaching for !important as the fast fix the moment a style "isn't working," instead of checking the real specificity of the conflicting rule first. In the overwhelming majority of real cases, a style isn't applying because a more specific selector elsewhere is winning — a genuine, diagnosable cause, not a reason to reach for a blunt override that creates a harder problem down the line.
Inheritance: a real, separate mechanism from the cascade
body {
font-family: "Inter", sans-serif;
color: #2b2b2b;
}Some properties (color, font-family, line-height) are inherited by default — set once on <body>, and every descendant element uses that same value unless a more specific rule overrides it for that element specifically. Other properties (border, padding, background) are never inherited by default, since inheriting them would rarely make real visual sense — a <p> automatically inheriting its parent's border would be a genuinely confusing default. This is exactly why setting real, sitewide typography once on body (covered fully in part 6) is standard practice, rather than repeating font-family on every individual element.
Next: the box model — content, padding, border, and margin, and the real property that changes how they're all measured together.