CSSIntermediate

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.

All CSS lessons

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:

  1. Is it !important? An important declaration beats a normal one. (And your styles beat the browser’s built-in ones.)
  2. Is it an inline style attribute? Inline styles beat rules from a stylesheet.
  3. Is it in a stronger cascade layer? (An advanced feature, covered at the end.)
  4. Which selector is more specific? The more specific selector wins.
  5. 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 p and p.warning tie 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:hover is 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:

  1. Look at it in DevTools and see what is beating it.
  2. Move your rule later, or make it slightly more specific (for example .panel .message instead of .message).
  3. 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

  1. Right-click the element and choose Inspect (see the DevTools lesson).
  2. Look in the Styles pane for your declaration. If it is crossed out, another rule beats it. The winning rule is listed above it.
  3. Compare the two selectors’ specificity, and check the order.
  4. Fix the cause: reorder, simplify, or add a class. Reach for !important last.

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 !important sparingly, 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-0 does not beat 1-0-0.
  • Reaching for !important first, then needing more !important to 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

  1. Click Practice in Editor and predict the colour of #target before looking. Then comment out .panel .message and predict again.
  2. Add #target { color: black; } and watch it beat everything. Then delete it and use .panel p.warning instead. Which wins now?
  3. Calculate the score of nav ul li a:hover, #header .menu > li and [type="text"]. Check yourself in the table logic above.
  4. Give .family a color, border and margin. Use DevTools to see which ones .child inherits. Then make the child copy the parent’s border with border: inherit.
  5. 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.
  6. Wrap two competing rules in @layer base and @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, initial and unset to control it.
  • !important is a last resort or for utility classes. It makes future changes harder.
  • @layer lets you order groups of styles independently of specificity.
  • Next you will learn display, position and overflow, the properties that control how boxes are placed.