CSS Specificity, the Cascade, Inheritance and !important
Find out why one CSS rule beats another. Learn how specificity is scored, how the cascade and inheritance work, when to use !important, and what @layer does.
What you will learn
Sooner or later every CSS learner writes a rule, refreshes the page and sees… nothing. The rule is correct, but another rule is winning. Understanding how the browser picks a winner is the biggest step from “guessing” to “knowing” in CSS. In this lesson you will learn the three tie-breakers (specificity, order and importance), how to calculate specificity, which properties are inherited, when !important is acceptable, and what cascade layers (@layer) add.
The HTML we will style
This lesson uses three tiny experiments:
<section class="panel">
<h2 class="panel-title">Who wins?</h2>
<p id="target" class="message warning">Which color am I?</p>
</section>
<div class="family">
<p class="child">I inherit color and font from my parent, but not its border or padding.</p>
</div>
<p class="notice" style="color: purple;">I have an inline style.</p>
| Element | Handles | Used for |
|---|---|---|
<p id="target" class="message warning"> |
id target; classes message and warning; inside .panel |
the specificity experiment: many rules match it |
<div class="family"> and <p class="child"> |
parent and child | the inheritance experiment |
<p class="notice" style="color: purple;"> |
a class and an inline style | the inline and !important experiment |
Why does one rule beat another?
Imagine five different rules all trying to set the colour of the same paragraph. The browser holds a small competition and asks these questions in order:
- Is it
!important? An important declaration beats a normal one. (And your styles beat the browser’s built-in ones.) - Is it an inline
styleattribute? Inline styles beat rules from a stylesheet. - Is it in a stronger cascade layer? (An advanced feature, covered at the end.)
- Which selector is more specific? The more specific selector wins.
- Still tied? The rule that comes last in the CSS wins.
This process is the cascade, the “C” in CSS. Most day-to-day problems are solved by questions 4 and 5, so we will spend the most time there.
Specificity: scoring a selector
Every selector has a specificity score, made of three numbers, counted like this:
| Column | What it counts | Examples |
|---|---|---|
| A: IDs | #id selectors |
#target |
| B: classes and friends | .class, [attribute], :hover and other pseudo-classes |
.message, [type="email"], :first-child |
| C: types | tag names and pseudo-elements | p, h2, ::before |
The universal selector * and combinators ( , >, +, ~) add nothing.
Write the score as A-B-C. Here are the rules from the experiment:
| Selector | A (IDs) | B (classes) | C (types) | Score |
|---|---|---|---|---|
p |
0 | 0 | 1 | 0-0-1 |
.message |
0 | 1 | 0 | 0-1-0 |
.panel p |
0 | 1 | 1 | 0-1-1 |
p.warning |
0 | 1 | 1 | 0-1-1 |
.panel .message |
0 | 2 | 0 | 0-2-0 |
#target |
1 | 0 | 0 | 1-0-0 |
#target.message |
1 | 1 | 0 | 1-1-0 |
How to compare scores: look at column A first. The higher number wins. Only if A is tied do you look at B, and only if B is tied do you look at C. The columns are not digits of one number: one ID beats any number of classes, and one class beats any number of tag names.
The experiment
The paragraph <p id="target" class="message warning"> is inside .panel, so all five starter rules match it:
p { color: gray; } /* 0-0-1 */
.message { color: royalblue; } /* 0-1-0 */
.panel p { color: seagreen; } /* 0-1-1 */
p.warning { color: orange; } /* 0-1-1 */
.panel .message { color: crimson; } /* 0-2-0 */
The winner is .panel .message with 0-2-0, so the paragraph is crimson, even though it is not the last rule. Notice:
.message(0-1-0) loses to.panel p(0-1-1), because at equal B the C column decides..panel pandp.warningtie at 0-1-1. If they were the only two, the later one (p.warning) would win.- Add
#target { color: black; }(1-0-0) and it beats all of them, however many classes they use.
Use Practice in Editor and comment rules out one by one to watch the winner change.
Other things that affect the score
- An inline style (
style="...") beats every selector in a stylesheet. Think of it as 1-0-0-0. :not(.x)has the specificity of what is inside the brackets.:is(.a, #b)uses its most specific item.:where(...)always counts as zero, which makes it great for low-priority defaults (you will meet these in Modern Selectors).- Pseudo-classes count like classes, so
a:hoveris 0-1-1.
Order: the last rule wins a tie
When two rules have equal specificity, the one that appears later wins. This is why a stylesheet is usually organised from general to specific: resets and base styles first, components next, special cases last. It also means <link> order matters: a stylesheet linked later can override one linked earlier.
.message { color: royalblue; }
.message { color: crimson; } /* same score, later, wins */
Inheritance
Not every property is decided by a rule aimed at the element. Some values are passed down from the parent when nothing else says otherwise. This is inheritance.
.family {
color: teal;
font-family: Georgia, serif;
border: 2px solid teal;
padding: 10px;
}
The <p class="child"> inside has no rule of its own, yet its text is teal and in Georgia. It inherited color and font-family. It did not inherit the border or padding, which makes sense: you would not want every child to draw its own box.
A useful rule of thumb: properties about text are inherited; properties about boxes are not.
| Usually inherited | Usually NOT inherited |
|---|---|
color |
margin, padding |
font-family, font-size, font-weight, font-style |
border, outline |
line-height, letter-spacing |
background |
text-align, text-transform |
width, height |
list-style, cursor, visibility |
display, position |
Inheritance is the reason we set the font once on body. It is also why an inherited value is the weakest of all: any rule aimed directly at the element beats it, regardless of specificity.
Controlling inheritance with keywords
Every property accepts these special values:
.child {
border: inherit; /* copy the parent's value, even for non-inherited properties */
}
.child {
color: initial; /* reset to the property's default (for color: black) */
}
.child {
color: unset; /* inherit if the property is normally inherited, otherwise initial */
}
A classic real-world use is making links or buttons match the surrounding text:
a {
color: inherit;
}
button {
font: inherit;
}
!important
Adding !important to a declaration lifts it above normal declarations:
/* the <p class="notice" style="color: purple;"> */
.notice {
color: red !important;
}
Normally the inline style="color: purple" would beat this class. With !important, the class wins and the paragraph turns red.
It sounds like a magic fix, and that is exactly the danger. Once you use it, the only way to beat that declaration is another !important with higher specificity or a later position. Stylesheets full of !important turn into an arms race that nobody can maintain.
Acceptable uses:
- Utility classes that must always win, such as
.hidden { display: none !important; }. - Overriding inline styles or third-party CSS that you cannot edit.
- User accessibility settings in the visitor’s own stylesheet.
Better alternatives when a rule is not winning:
- Look at it in DevTools and see what is beating it.
- Move your rule later, or make it slightly more specific (for example
.panel .messageinstead of.message). - Remove the inline style or the over-specific rule that causes the problem.
Cascade layers with @layer
Cascade layers let you group your CSS and decide, explicitly, which group wins, so the order of layers matters more than specificity. First you declare the order (last one is the strongest), then you put rules inside:
@layer base, theme;
@layer base {
.message {
color: royalblue; /* more specific... */
}
}
@layer theme {
p {
color: crimson; /* ...but this layer is declared later, so it wins */
}
}
The paragraph is crimson, although .message (0-1-0) is more specific than p (0-0-1). The theme layer was listed after base, so it beats it. Two more facts:
- Styles not in any layer beat all layered styles.
- Layers are ideal for organising a big project (
reset,base,components,utilities) and for taming third-party CSS. The CSS Nesting and Layers lesson covers this in depth.
Debugging a rule that will not win
- Right-click the element and choose Inspect (see the DevTools lesson).
- Look in the Styles pane for your declaration. If it is crossed out, another rule beats it. The winning rule is listed above it.
- Compare the two selectors’ specificity, and check the order.
- Fix the cause: reorder, simplify, or add a class. Reach for
!importantlast.
Best practices
- Style with classes. They have low, predictable specificity.
- Avoid IDs in CSS. They are strong and hard to override. Use them for links and JavaScript.
- Avoid inline styles and long selector chains.
- Keep specificity flat: ideally most of your selectors score 0-1-0.
- Organise from general to specific so the order works for you.
- Use
!importantsparingly, mostly for utilities.
Putting it together
/* Base styles, low specificity */
p {
color: gray;
}
/* Component styles */
.message {
color: royalblue;
}
/* A more specific variation in one place */
.panel .message {
color: crimson;
}
/* Inheritance: set once on the parent */
.family {
color: teal;
font-family: Georgia, serif;
}
/* A rare, deliberate use of !important */
.notice {
color: red !important;
}
Common mistakes
- Thinking the last rule always wins. It only wins a tie in specificity.
- Reading A-B-C as one number.
0-10-0does not beat1-0-0. - Reaching for
!importantfirst, then needing more!importantto fix the fallout. - Using IDs for styling, and then writing ever more specific selectors to override them.
- Expecting margins, borders or backgrounds to be inherited.
- Editing the wrong rule. Always check in DevTools which rule is actually being applied.
- Adding inline styles “just this once”, which then can only be beaten by
!important.
Practice
- Click Practice in Editor and predict the colour of
#targetbefore looking. Then comment out.panel .messageand predict again. - Add
#target { color: black; }and watch it beat everything. Then delete it and use.panel p.warninginstead. Which wins now? - Calculate the score of
nav ul li a:hover,#header .menu > liand[type="text"]. Check yourself in the table logic above. - Give
.familyacolor,borderandmargin. Use DevTools to see which ones.childinherits. Then make the child copy the parent’s border withborder: inherit. - Put an inline
style="color: purple"on a paragraph, then override it from the stylesheet with!important. Then find a way to remove the need for!important. - Wrap two competing rules in
@layer baseand@layer theme, and swap the layer order in@layer base, theme;. Observe the result.
Recap
- The browser picks a winner using importance, inline styles, layers, specificity and finally order.
- Specificity is scored as A-B-C: IDs, then classes/attributes/pseudo-classes, then tags/pseudo-elements. Compare column by column from the left.
- With equal specificity, the later rule wins.
- Text properties are inherited; box properties are not. Use
inherit,initialandunsetto control it. !importantis a last resort or for utility classes. It makes future changes harder.@layerlets you order groups of styles independently of specificity.- Next you will learn
display,positionandoverflow, the properties that control how boxes are placed.