Selectors and the cascade
In short: A CSS selector picks the elements a rule applies to, and the cascade decides which rule wins a conflict: origin, specificity and order.
Problem
The price does not turn red
The shop starts a sale: the price of the bread must be red. A colleague adds a rule for .price at the very bottom of style.css. The price gets bigger and bold, but it stays dark gray.
Console
The console is empty: CSS gives no errors. The rule is the last one in the file and it does match the price, because the size and the bold weight come through. Only the colour does not. Another rule wants another colour for the same paragraph, and the browser picked that one.
The idea
Rule and selector
CSS says how each element of the tree looks. It says it with rules:
.price { color: #c0392b; font-size: 20px;}.priceis the selector: which elements the rule matches.- Inside the braces are the declarations: property, colon, value and a semicolon.
Selectors search the tree, so they are like questions to it:
p(type): every<p>..price(class): every element withclass="price". An element can have several classes:class="price sale".#product(id): the element withid="product".[type="email"](attribute): the elements with this attribute and this value, for exampleinput[type="email"].article p(descendant, with a space): a<p>anywhere inside an<article>, at any depth.nav > a(child): an<a>that is a direct child of<nav>..price.sale(no space): one element with both classes.h1, h2(list): the rule applies to both.
Styles are written mostly with classes. A type matches every paragraph on the site, and an id is for one element. A class goes exactly where you need it, and it does not depend on how the tree is arranged.
Try it
Match exactly these
Remove the underline only from the links in the menu (the link at the bottom stays underlined) and make both prices bold without making the descriptions bold. The properties are text-decoration: none and font-weight: 700.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>The shop</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<nav>
<a href="/bread">Bread</a>
<a href="/milk">Milk</a>
<a href="/cart">Cart</a>
</nav>
</header>
<main>
<article>
<h2>Dobrudzha bread</h2>
<p>White bread, 650 g, baked this morning.</p>
<p class="price">€1.80</p>
</article>
<article>
<h2>Fresh milk 3%</h2>
<p>One litre, from a farm near Teteven.</p>
<p class="price">€2.40</p>
</article>
</main>
<footer>
<a href="/terms">Delivery terms</a>
</footer>
</body>
</html>
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
nav { display: flex; gap: 16px; }
nav a { text-decoration: none; }
.price { font-weight: 700; }
nav a matches only the links in <nav>, while a would also match the link at the bottom. .price matches the two paragraphs with the class, while p would make the descriptions bold too. Without the dot, price looks for a <price> element, and there is none.
The idea
The cascade
The price from the start is matched by two rules, and both set color. The browser has to pick one. This rule for picking is called the cascade. It compares on three levels and stops at the first one that decides:
- Origin. The browser has its own styles: that is why
<h1>is big and a link is blue and underlined. Your CSS beats them. - Specificity. How precise the selector is. It is three numbers: how many ids, how many classes, how many types.
pis 0,0,1,.priceis 0,1,0,#product pis 1,0,1. Attributes ([type="email"]) and states (:hover) count as classes. The numbers are compared column by column from the left. As soon as one column differs, it is decided: 1,0,1 beats 0,1,0. - Order. When the specificity is equal, the rule that comes later in the code wins.
The cascade decides each property separately. That is why font-size and font-weight from .price come through: nothing else competes for them. For color something does, and it is stronger.
There is also !important: color: #c0392b !important beats every normal declaration, whatever the specificity. It works, but after that every change to the colour needs a new !important. So it is a sign that a selector somewhere is too strong, not a fix.
See it
Watch the browser pick
Four rules and one element. Watch the browser decide color and font-size separately.
/* <article id="product"> <p class="price sale">€1.80</p> </article> */p { color: #555; font-size: 16px; }.price { color: #c0392b; font-size: 20px; }#product p { color: #222; }.sale { font-size: 24px; }Step by step, in words
- One element: a <p> with the classes price and sale, inside <article id="product">. All four selectors match it. Two of the rules want to set color and three want font-size.
- The specificity of a selector is three numbers: how many ids, how many classes, how many types. #product p has one id and one type: 1,0,1.
- color: three rules set it. The browser compares the first column: 1 beats 0, and it does not look further. #product p wins, even though .price comes after it.
- font-size: three rules again. The id column is equal (0). In the class column p drops out with 0. .price and .sale are equal in all three columns.
- Equal specificity: the rule that comes later in the code wins. .sale is on line 7, after .price, so font-size is 24px.
- Each property has its own winner: color from #product p, font-size from .sale. The rule .price applies to the element and still loses in both.
Here is the same code in the browser. The price is dark gray and 24px, as in the scene. Try swapping .price and .sale: the size changes and the colour does not. Open the developer tools (F12), select the price and look at the Styles panel: the losing declarations are crossed out.
Console
The idea
Inheritance
Some properties pass from the parent to the children. If an element has no rule of its own for them, it takes the value of its parent. This is inheritance.
- The properties of text are inherited:
color,font-family,font-size,font-weight,line-height,text-align. - The properties of the box are not:
border,padding,margin,background,width. A border around the card does not mean a border around every paragraph in it.
An inherited value is the weakest. Any rule that matches the element directly beats it, even p with 0,0,1. Buttons and fields get their own font and colour from the browser, so they do not inherit. inherit asks for inheritance explicitly: button { font-family: inherit; }.
Console
The text of the card is green and in Georgia, but the border is only around the card. The button took the font because of inherit, and its colour stayed black: there is no such rule for color.
Try it
Fix the price
Make the price red (#c0392b) without !important. The description must stay dark gray (#222).
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dobrudzha bread</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<article id="product">
<h1>Dobrudzha bread</h1>
<p>White bread, 650 g, baked this morning.</p>
<p class="price">€1.80</p>
<button type="button">Add to cart</button>
</article>
</body>
</html>
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
#product { color: #222; line-height: 1.5; }
h1 { font-size: 24px; }
/* Sale: the price is red */
.price { color: #c0392b; font-size: 20px; font-weight: 700; }
You do not need a stronger selector for the price, but a weaker one for the description. #product { color: #222; line-height: 1.5; } gives the colour to the article, and the paragraphs inherit it. The inherited value loses to .price, which matches the price directly. #product .price (1,1,0) also passes, but then the next change will need an even stronger selector. p.price is not enough: 0,1,1 still loses to 1,0,1 in the first column.
Try it
Find the bug
The price has the classes price and sale, but it is not red. Why?
Pick the line with the bug.
The space in .price .sale means "an element with the class sale inside an element with the class price". Here both classes are on one element, and for that they are written together: .price.sale.
Try it
Fill in
The menu has links and a nested list with more links. Only the links that are direct children of <nav> must have no underline.
nav a {
text-decoration: none;
}> matches only direct children. With a space (nav a) the rule would also match the links in the nested list.
More precisely, if you are curious
Origin. There are three sources: the browser, the user (settings and extensions) and the author, which is you. For normal declarations the author beats the user, and the user beats the browser. With !important the order flips: the user's !important beats yours. This way a person with poor eyesight can force a bigger font on every site.
style="…". A style in the element's attribute beats every selector in the file. So it is a trap too, like !important.
Columns do not overflow. 0,11,0 is not 1,1,0. Eleven classes do not beat one id: the comparison ends in the first column.
What does not count. * is 0,0,0. Combinators (space, >, +, ~) add nothing. Pseudo-elements (::before) count as types.
:is(), :not() and :where(). :is() and :not() take the specificity of their strongest argument. :where() is always 0,0,0: handy for default styles that any rule overrides.
Layers. @layer orders whole groups of rules, and the layer decides before specificity. Big style libraries use it so they do not fight with your CSS.
The keywords. inherit takes the value of the parent. initial is the starting value of the property from the standard (for display it is inline). unset is inherit for inherited properties and initial for the others. revert brings back the browser's style.
Exercise
The sale in the catalog
The catalog has three products, and the milk is on sale (the class sale on its card). All text in the catalog is #222, and the prices are bold. The price of the product on sale is red (#c0392b), and the old price inside it is gray (#777). The −20% label has white text (#fff) on a red background (#c0392b). The other prices stay #222. Do not use !important.
Console
Solution
index.html<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Catalog</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main id="catalog">
<article class="product">
<h2>Dobrudzha bread</h2>
<p class="price">€1.80</p>
</article>
<article class="product sale">
<h2>Fresh milk 3%</h2>
<p class="badge">−20%</p>
<p class="price"><s class="old">€2.40</s> €1.92</p>
</article>
<article class="product">
<h2>Butter 125 g</h2>
<p class="price">€3.20</p>
</article>
</main>
</body>
</html>
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
#catalog { color: #222; }
.price { font-weight: 700; }
.sale .price { color: #c0392b; }
.old { color: #777; }
.badge { color: #fff; background: #c0392b; }
#catalog p matches every paragraph with 1,0,1 and beats every rule made of classes. So the colour goes on #catalog, and the text inherits it. Then .sale .price (0,2,0) matches only the price on sale, .old gives the gray to the old price (otherwise it would inherit the red from the paragraph), and .badge gives the label its colour and background. The headings are #222 too: they inherit from #catalog.
Test
Test yourself
Related lessons
Section titled "Related lessons"- Builds on: The HTML document · Forms
- Continues in: The box model · Flexbox: row and column · Grid: rows and columns · Responsive design
- Tasks on this topic: CSS: how the page looks