Cascade, specificity, inheritance
What CSS is, how the winning rule is decided and what gets inherited.
Updated
What CSS is
CSS (Cascading Style Sheets) describes how the HTML looks. A CSS rule has a selector (who) and declarations (what):
.card h2 { /* selector */
color: navy; /* property: value; */
margin-bottom: 1rem;
}The central problem of CSS: several rules can target the same element. Which one wins? The answer is the cascade.
The cascade — the order of the decision
When two declarations fight over the same property, the browser compares, in order:
- Importance —
!importantbeats everything (avoid it; it's a sign something is wrong). - The layer (
@layer) — layers declared later win. Tailwind uses it internally. - The selector's specificity.
- Source order — on a tie, the last rule wins.
Specificity
Every selector gets a score of the form (IDs, classes, elements). They're compared left to right — one class beats any number of elements, one ID beats any number of classes.
| Selector | Score | Explanation |
|---|---|---|
p |
(0, 0, 1) | one element |
.card |
(0, 1, 0) | one class |
.card p |
(0, 1, 1) | class + element |
nav a:hover |
(0, 1, 2) | the pseudo-class counts as a class |
[type="email"] |
(0, 1, 0) | the attribute counts as a class |
#header .logo |
(1, 1, 0) | the ID beats everything below it |
style="..." |
above all | inline style |
* |
(0, 0, 0) | universal, no score |
Special cases: :where(...) has specificity 0 (good for base styles that are easy to override); :is(...) and :not(...) take the specificity of their "strongest" argument.
The practical rule: use classes, avoid IDs in CSS and !important. That way all rules have similar scores and the order stays predictable.
Inheritance
Some properties pass automatically from parent to children, others don't:
| Inherited (usually text) | Not inherited (usually box) |
|---|---|
color, font-*, line-height, text-align, visibility |
margin, padding, border, width, background, display |
That's why you put font-family on body and it applies everywhere, while padding on body doesn't show up on every paragraph.
You can force it: inherit (take the parent's value), initial (the default value), unset.
Selector types
| Type | Example | Selects |
|---|---|---|
| element | p |
all paragraphs |
| class | .btn |
elements with class="btn" |
| ID | #app |
the element with id="app" |
| attribute | [disabled], [href^="http"] |
by attributes |
| descendant | .card p |
a p anywhere inside .card |
| direct child | .menu > li |
only direct children |
| next sibling | h2 + p |
the p right after an h2 |
| pseudo-class | :hover, :focus-visible, :first-child, :nth-child(2n) |
a state or position |
| pseudo-element | ::before, ::after, ::placeholder |
a "virtual" part of the element |
| relational | .card:has(img) |
the parent that contains something |
Common mistakes
- Long selectors (
.page .main .content .card .title) — hard to override. !importantto "beat" a rule — it hides the real problem.- Styles on IDs.
Summary
- The winner:
!important→ layer → specificity → order. - Specificity = (IDs, classes, elements); use classes.
- Text is inherited, the box isn't.