CSS conceptSelectors

Specificity

A selector’s weight, which decides which rule wins when several rules set the same property on the same element.

On this page

Example

#
CSS
/* (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;
}
Preview

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

#
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

#
  • .note vs p

    (0, 1, 0) > (0, 0, 1)

    A class weighs more than a type selector, so the background is green, even though the p rule comes later.
  • #intro vs .note.demo

    (1, 0, 0) > (0, 2, 0)

    One ID beats any number of classes. The background is blue.
  • .demo vs .note

    (0, 1, 0) = (0, 1, 0)

    A tie. With equal weight, the rule written later wins, so the background is green.
  • :where(#intro) vs p

    (0, 0, 0) < (0, 0, 1)

    :where() zeroes out the weight, so the rule with the ID, even though it comes later, loses to plain p. The background is green.
  • :is(p, #missing) vs .note

    (1, 0, 0) > (0, 1, 0)

    :is() takes the weight of the strongest selector on its list, even when the element matches through the weaker p. The background is blue.
  • !important

    weight doesn’t matter

    !important beats an ordinary declaration regardless of the selector’s weight. The background is green despite the ID.
  • style=""

    style attribute

    A style written into the style attribute is stronger than any selector in the stylesheet. The background is green.

Rule duels in practice

#

.note vs p

A class weighs more than a type selector, so the background is green, even though the p rule comes later.
.note { background: seagreen; }
p { background: crimson; }

#intro vs .note.demo

One ID beats any number of classes. The background is blue.
#intro { background: royalblue; }
.note.demo { background: crimson; }

.demo vs .note

A tie. With equal weight, the rule written later wins, so the background is green.
.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=""

A style written into the style attribute is stronger than any selector in the stylesheet. The background is green.
#intro { background: royalblue; }

More examples

#
The fix: lowering the weight with :where()
CSS
/* (0, 0, 1): :where() zeroes out the weight of #menu */
:where(#menu) a {
  color: #a1a1aa;
}

/* (0, 1, 0): now it wins */
.active {
  color: #fbbf24;
}
Preview

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. !important solves 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

#

Spotted a mistake or missing an example? Write to us.