The Cascade, Specificity and Inheritance
Sooner or later two of your rules will disagree about the same element. One sets a paragraph's colour to navy, another sets it to green, and one of them seems to be ignored. It is not ignored; it lost. CSS has a fixed procedure for deciding which value wins when rules conflict, called the cascade, and the part of it you need every day is short. It is the "cascading" in Cascading Style Sheets.
Instructions at work already follow this logic. The company policy says one thing, your team lead says something more specific, and your manager sends a note about this one task. The most specific instruction wins. When two are equally specific, the most recent one does. CSS settles conflicts in exactly that spirit, with the details made precise.
Order: The Later Rule Wins
Start with the plainest case: two rules with the same selector, setting the same property.
p {
color: navy;
}
p {
color: darkgreen;
}
Both rules select every paragraph, and both set its colour. The browser reads the stylesheet from top to bottom, and when two equally specific rules clash, the one that comes later wins. Every paragraph on the page is dark green. Swap the two rules and every paragraph turns navy. Only the conflicting property is decided this way: if the first rule also set a font, that font would still apply, because nothing disagrees with it.
Specificity: The More Precise Selector Wins
Order decides only between equals. When the selectors differ, the more precise one wins, wherever it sits in the file. How precise a selector is has a name: its specificity. The ranking is short: an id selector beats a class selector, and a class selector beats a type selector.
<p id="intro" class="lead">Welcome to my site.</p>
#intro {
color: purple;
}
.lead {
color: darkorange;
}
p {
color: navy;
}
This paragraph has an id and a class, and all three rules match it. The paragraph is purple, even though the id rule comes first, because an id is the most specific of the three. Delete the id rule and the paragraph turns dark orange: the class rule now beats the type rule, again regardless of order. Only when you delete the class rule too does the paragraph become navy.
When a selector has several parts, specificity is counted, not memorised. Count three things: ids, then classes together with attributes and pseudo-classes, then element names. The selector nav a counts zero, zero, two. The selector .site-nav a counts zero, one, one, and beats it, because the counts are compared from the left and its one class outranks any number of element names. You rarely need to count by hand: in Chrome's Styles pane, hover over a selector and a small label shows its three numbers.
Now the reason this book styles with classes and not ids becomes clear. An id rule beats every class rule, so once a colour is set by id, the only way to change it is another id rule or something stronger, and the stylesheet turns into an arms race. Classes keep everything at one level, where order settles the rest. One thing beats even an id: a style attribute written on the element itself, which is one more reason to avoid those.
This is the simplified picture, and it covers the rules you write. The full cascade has a step before specificity, about which stylesheet a rule comes from. The part you will notice is that any rule of yours beats the browser's default stylesheet, whatever the specificities.
Inheritance: What Flows Down
Some properties pass from an element to everything inside it, which is called inheritance. It is why setting a colour on body in topic 1 coloured every paragraph and heading without a rule for each.
body {
color: darkslategray;
border: 2px solid gray;
}
With this rule, every piece of text on the page is dark slate grey, because text colour is inherited. But only one border appears: a single grey line around the whole body. The paragraphs inside do not each get their own border, because border is not inherited. The pattern is quick to learn. Text properties, such as colour, font and line spacing, mostly inherit. Box properties, such as borders, spacing and backgrounds, mostly do not.
An inherited value is the weakest thing in the cascade: any rule that targets the element directly beats it, however unspecific. That is why your links stayed blue in topic 1. The browser's default stylesheet has a rule aimed at links themselves, and a rule aimed at the link beats a colour inherited from the body. To make links follow the text colour, give them the value inherit, which means "take my parent's value": a rule selecting a with color set to inherit.
Finding the Winner in DevTools
You never have to work a conflict out in your head. Inspect the element and look at the Styles pane. Every rule that matches it is listed, the winners at the top. A declaration that lost is shown crossed out, with a line through it, and a rule further up the pane is the one that beat it. Inherited values appear in their own group, under a heading naming the element they came from. One look-alike is worth knowing: a crossed-out line with a small warning triangle beside it is not a loser but a declaration the browser could not understand, usually because of a typo.
Beside the Styles tab is the Computed tab. It lists the final value of every property on the selected element, the value actually used. Expand a property there and DevTools shows which rule set it, with a link to the file and line. When a style is not doing what you expect, those two tabs answer the question in seconds.
The Important Flag, and Why It Is a Smell
CSS has an override switch, the important flag: write an exclamation mark and the word important at the end of a declaration, as in color: red !important;, and that declaration beats every normal one, whatever its specificity. It is tempting when a rule will not apply. Programmers call it a smell: a sign that something underneath is wrong.
The trouble is what happens next. The next time you need to change that colour, a normal rule cannot, so you need another important declaration, and a more specific selector to win among the important ones. Each fix makes the next one harder. The better fix is almost always to find, in DevTools, the rule that is winning and why. Large projects also use cascade layers, a newer feature that lets a stylesheet declare which groups of rules outrank which, in order. It is worth knowing the name; this book does not need it.
- "The browser ignored my CSS." Another rule won the cascade. Inspect the element: your declaration will be crossed out, and the rule that beat it is listed above.
- "Adding the important flag fixes it." It fixes today's conflict and causes tomorrow's, because the next change needs something stronger still.
- "Everything inherits." Text properties mostly do; box properties such as borders, backgrounds and spacing mostly do not.
- Almost every "my CSS doesn't work" is the cascade at work, and DevTools answers it in seconds once you know where to look.
- Keeping selectors to classes keeps the cascade predictable, so the order of your file is enough to reason about.
Knowledge Check
Two rules both select p and set color, one to navy and one, lower in the file, to green. What colour are the paragraphs?
- Navy, because it was the first rule the browser read
- Green, because the later rule wins between equals
- A mix of the two, since both rules apply to them
- The default black, since the two rules cancel out
A paragraph matches both #intro, which sets purple, and .lead, which sets orange and comes later. What colour is it?
- Orange, because the class rule comes later in the file
- A blend of purple and orange, since both of them match
- Purple, because an id is more specific than a class
- The browser's default colour, because the rules clash
body sets a colour and a border. What does a paragraph inside the body get?
- It takes the colour, but gets no border of its own
- Both the colour and a border of its own
- A border of its own, but not the colour
- Neither, because it has no rules of its own
In the Styles pane, a declaration of yours is crossed out, with no warning icon beside it. What does that mean?
- The browser could not understand what you wrote
- DevTools removed it from your stylesheet file
- The stylesheet that holds it failed to download
- Another rule set the same property on it and won
You got correct