What problem does the cascade solve?
Many rules can set the same property on the same element. The browser's stylesheet makes links blue, your site stylesheet makes them navy, and a component rule makes the nav links white. The cascade is the algorithm that picks one winner for each property on each element.
It works through a short list of questions in a fixed order and stops as soon as one rule comes out ahead: where does the rule come from and is it marked important, how specific is its selector, and which one appears last. Knowing that order turns "CSS is random" into a predictable checklist.


How do origin and importance rank rules?
Declarations come from three origins. The user agent origin is the browser's built-in stylesheet. The user origin is a reader's own preferences, such as a minimum font size or a custom stylesheet. The author origin is your CSS. For normal declarations, author beats user, and user beats the browser.
Marking a declaration !important flips the ranking for that declaration: important user styles beat important author styles. That rule exists so readers with accessibility needs can always win. It also means !important has a defined place in the system, and using it to win routine arguments inside your own CSS bends that system.
Cascade layers (@layer) add another step between origin and specificity, letting you put a reset or a third-party library in a lower layer that your own code always beats. They are supported in all current browsers since 2022 and get their own lesson later.
How do you calculate specificity?
Specificity is a score with three columns, written id-class-type. Count the ids in the selector for the first column. Count classes, attribute selectors and pseudo-classes for the second. Count type selectors and pseudo-elements for the third. The universal selector * and combinators count for nothing.
Compare scores column by column from the left, like version numbers. #hours p is 1-0-1. .card .btn is 0-2-0. ul li a is 0-0-3. One id beats any number of classes, and one class beats any number of types. There is no carrying over: eleven classes never add up to an id.
A few pseudo-classes have special rules. :is(), :not() and :has() take the specificity of their most specific argument. :where() always counts as zero. DevTools shows the score when you hover a selector in the Styles panel, which saves you counting by hand.
pscores 0-0-1.notescores 0-1-0a:hoverscores 0-1-1#hours .notescores 1-1-0[type="email"]scores 0-1-0
What happens when two rules tie?
The one that appears later in the CSS wins. That covers rules later in the same file, rules in a later <style> element, and rules in a stylesheet linked after another one. Order is the final tiebreaker, so the cascade always produces exactly one answer.
Use this deliberately. Put base styles first and modifiers after them, so .price comes before .sale and .btn comes before .btn-large. When a modifier appears above its base class, the base silently wins and the modifier looks broken.
Order only matters between declarations for the same property. If .price sets font-size and .sale sets color, both apply no matter which comes first.
.price {
color: #222;
}
/* later, same specificity: wins */
.sale {
color: #a3341f;
}Where do inline styles and !important fit?
A style attribute on an element beats every normal declaration from your stylesheets, whatever the selector's specificity. Think of it as a fourth column to the left of ids. The only way to override it from a stylesheet is !important.
An !important declaration beats every normal declaration. Two important declarations then compete on specificity and order like normal ones. That is how stylesheets end up with chains of !important overriding each other, each one harder to remove.
Reasonable uses exist: utility classes such as .visually-hidden that must always work, or overriding inline styles injected by a third-party widget. For everything else, fix the selector. When an AI tool sprinkles !important through a stylesheet, treat it as a sign the underlying selectors are fighting, and simplify them.
.visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
overflow: hidden !important;
}How does :where() help you write easy-to-override CSS?
Anything inside :where() contributes zero specificity. :where(#hours) p scores 0-0-1, the same as a plain p. That makes it ideal for defaults you expect to override: resets, base typography, or styles in a shared library.
If the bakery's base stylesheet writes :where(.card) a { color: #1d3a8a; }, any page can override the link colour with a single class, without worrying about the card's selector. Supported in all current browsers since 2021.
The sibling :is() looks the same but keeps the specificity of its strongest argument, so :is(#hours, .note) p scores 1-0-1 for every element it matches. Choose :where() when you want to lose ties easily.
:where(.card) a {
color: #1d3a8a;
}
.btn {
color: #fff; /* wins without extra weight */
}How do you debug a style that isn't applying?
Work through the causes in order. First, inspect the element and check whether your rule appears in the Styles panel at all. If it doesn't, the selector doesn't match or the stylesheet didn't load.
If the rule appears with your declaration crossed out, another rule won. The winning declaration is shown above it without a line through it. Compare their specificity, then their order, then look for !important or an inline style attribute.
If the declaration has a warning icon, the property or value is invalid. If it applies but nothing changes visually, the property may not do anything in this context: width on an inline <span>, or top on an element without position.
Fix the cause with the lightest tool: correct the selector, move the rule later, or add one class to the selector. Reach for !important last.
- Rule missing: selector or loading problem.
- Crossed out: lost the cascade.
- Warning icon: invalid property or value.
- Applied but no effect: property doesn't apply to this element.
How do you keep specificity low and predictable?
Style mostly with single classes. When every component rule scores 0-1-0 or 0-2-0, order decides most ties and you rarely need to think about weights. Avoid ids in selectors, avoid long descendant chains, and keep !important for a handful of utilities.
A useful habit is to write a rule, then ask how someone would override it. If the answer needs an id or !important, the rule is heavier than it needs to be.
Field notes
- When rules clash, the browser picks a winner using a fixed order of checks.
- Specificity counts ids, then classes, then element names in a selector.
- If two rules score the same, the one written later wins.
- Styling mostly with single classes keeps conflicts small and easy to follow.
Easy to get wrong
Matching the specificity of the competing rule fixes the conflict without forcing every future override to use !important.
Equal specificity means the later rule wins. Modifier classes go after the base class.
Two ids give a score of 2-1-1 that nothing short of more ids can override. A single class keeps the stylesheet flexible.
Questions people ask
Do eleven classes beat one id?
No. Columns never carry over. One id outranks any number of classes.
Does the order of classes in the HTML attribute matter?
No. class="sale price" and class="price sale" behave the same. Only the order of the rules in the CSS counts.
Do inherited values take part in specificity?
No. Any rule that targets the element directly beats a value it inherits from its parent, even a p selector against an id on the parent.
Is !important ever the right answer?
Yes, for small utility classes that must always apply and for overriding inline styles you cannot edit. Elsewhere, fix the selector instead.