Specificity
A selector’s weight, which decides which rule wins when several rules set the same property on the same element.
Example
#/* (1, 0, 1) */
#menu a {
color: #a1a1aa;
}
/* (0, 1, 0): loses, even though it comes later */
.active {
color: #fbbf24;
}
/* (1, 1, 1): wins */
#menu a.active {
font-weight: 700;
border-bottom: 2px solid #fbbf24;
}<nav id="menu" class="stage" style="display: flex; gap: 16px;">
<a href="#" style="text-decoration: none;">Courses</a>
<a href="#" class="active" style="text-decoration: none;">Documentation</a>
<a href="#" style="text-decoration: none;">Collections</a>
</nav>The active link has the bold weight and underline from the third rule, but still the gray color: .active lost to the stronger #menu a.
Definition and usage
#When two rules set the same property on the same element, the browser has to pick a winner. One of the main criteria is specificity, the selector’s weight, written as three numbers (a, b, c): a is the number of IDs, b is the number of classes, attributes, and pseudo-classes, and c is the number of type selectors and pseudo-elements.
Weights are compared from the left, column by column. (1, 0, 0) beats (0, 15, 0), because the first column settles it before we even look at the second. It isn’t a simple sum of points. The universal selector * and combinators (space, >, +, ~) add nothing. On a tie, the rule written later wins.
Functional pseudo-classes have their own rules. :is(), :not(), and :has() take on the weight of the strongest selector in their list, while :where() always weighs zero, which lets you write styles that are easy to override. :nth-child(… of S) is the weight of the pseudo-class plus the strongest selector from S.
Other stages of the cascade sit above specificity. Styles from the style attribute beat every selector in the stylesheet, and a declaration with !important beats ordinary declarations regardless of the selector’s weight (among several !important declarations, specificity decides again). @layer layers work similarly: a rule from a later layer wins, even if it has a weaker selector.
Syntax
#/* specificity = (a, b, c) */
#id /* a: (1, 0, 0) */
.class [attribute] :hover /* b: (0, 1, 0) */
p ::before /* c: (0, 0, 1) */
* > + ~ :where() /* no weight: (0, 0, 0) */Rule duels
#(0, 1, 0) > (0, 0, 1)
A class weighs more than a type selector, so the background is green, even though theprule comes later.(1, 0, 0) > (0, 2, 0)
One ID beats any number of classes. The background is blue.(0, 1, 0) = (0, 1, 0)
A tie. With equal weight, the rule written later wins, so the background is green.(0, 0, 0) < (0, 0, 1)
:where()zeroes out the weight, so the rule with the ID, even though it comes later, loses to plainp. The background is green.(1, 0, 0) > (0, 1, 0)
:is()takes the weight of the strongest selector on its list, even when the element matches through the weakerp. The background is blue.weight doesn’t matter
!importantbeats an ordinary declaration regardless of the selector’s weight. The background is green despite the ID.style attribute
A style written into thestyleattribute is stronger than any selector in the stylesheet. The background is green.
Rule duels in practice
#.note vs p
p rule comes later..note { background: seagreen; }
p { background: crimson; }#intro vs .note.demo
#intro { background: royalblue; }
.note.demo { background: crimson; }.demo vs .note
.demo { background: royalblue; }
.note { background: seagreen; }:where(#intro) vs p
:where() zeroes out the weight, so the rule with the ID, even though it comes later, loses to plain p. The background is green.p { background: seagreen; }
:where(#intro) { background: crimson; }:is(p, #missing) vs .note
:is() takes the weight of the strongest selector on its list, even when the element matches through the weaker p. The background is blue.:is(p, #missing) { background: royalblue; }
.note { background: crimson; }!important
!important beats an ordinary declaration regardless of the selector’s weight. The background is green despite the ID.p { background: seagreen !important; }
#intro { background: royalblue; }style=""
style attribute is stronger than any selector in the stylesheet. The background is green.#intro { background: royalblue; }More examples
#/* (0, 0, 1): :where() zeroes out the weight of #menu */
:where(#menu) a {
color: #a1a1aa;
}
/* (0, 1, 0): now it wins */
.active {
color: #fbbf24;
}<nav id="menu" class="stage" style="display: flex; gap: 16px;">
<a href="#" style="text-decoration: none;">Courses</a>
<a href="#" class="active" style="text-decoration: none;">Documentation</a>
<a href="#" style="text-decoration: none;">Collections</a>
</nav>After wrapping #menu in :where(), a plain class is enough to highlight the active link.
Browser support
#Widely available
Works in all modern browsers, phones included. You can use it without worry.
- Chrome
- Edge
- Firefox
- Safari
Good practices
#- Keep specificity low and even: style mostly with single classes. Then the outcome is decided by rule order, which is easy to control.
- Before reaching for
!important, check in dev tools which rule is winning and why.!importantsolves the problem today, but tomorrow it will force another!important. - Wrap base and library styles in
:where(). They then carry zero weight, and anyone can override them with a single class.
Related entries
#- #idSelects the element with the given identifier from its id attribute. It carries very high specificity, so its rules are hard to override later.
- .classSelects elements that have the given class in their class attribute. The most commonly used selector for styling components.
- :where()Works like :is(), but always has zero specificity. Ideal for base styles that should be easy to override.
- :is()Matches an element against any selector in a list. Shortens long, repetitive selectors into a single notation.
- :not()Selects elements that match neither the given selector nor any selector in the list.
- A, BA comma combines several selectors into a single rule, to give them the same styles without repeating declarations.
Spotted a mistake or missing an example? Write to us.