Specificity in CSS

Developers who come to CSS from more traditional programming languages sometimes note that CSS has a global scope. In other words, using button as a selector applies those declarations to every <button> element, whether that was intended or not.

The “global” nature of CSS is really an issue of specificity and the cascade in Cascading Style Sheets. Although it may seem arbitrary at first, CSS has well-defined rules for determining what declarations to apply. Understanding specificity may be what separates CSS developers from CSS masters.

Calculating exact specificity values can seem tricky at first. As explained in the Selectors Level 4[1] specification, you need to:

  • count the number of ID selectors in the selector (= A)
  • count the number of class selectors, attribute selectors, and pseudo­classes in the selector (= B)
  • count the number of type selectors and pseudo-elements in the selector (= C)
  • ignore the universal selector

We then need to combine A, B, and C to get a final specificity value. Take the following rule:

input {

font-size: 16px;

}

The selector for this rule, input , is a “type” or “element” selector. Since there’s only one type selector, the specificity value for this rule is 0,0,1. What if we add an attribute selector, as shown below?

input[type=text] {

font-size: 16px;

}

Adding an attribute selector raises the value for this rule to 0,1,1. Let’s add a pseudo-class:

input[type=text]:placeholder-shown {

font-size: 16px;

}

Now our selector’s specificity is 0,2,1. Adding an ID selector, as shown below, increases the specificity value to 1,2,1:

#contact input[type=text]:placeholder-shown {

font-size: 16px;

}

Think of specificity as a score or rank that determines which style declarations get applied to an element. The universal selector ( * ) has a low degree of specificity. ID selectors have a high degree. Descendant selectors such as p img , and child selectors such as .panel > h2 , are more specific than type selectors such as p , img , or hi . Class names fall somewhere in the middle.

However, when two selectors are equally specific, the cascade kicks in, and the last rule wins. Here’s an example:

a:link {

color: #369;

}

a.external {

color: #f60;

}

Both a:Link and a.externaL have a specificity value of 0,1,1: zero ID selectors, one class or pseudo-class, and one type (or element) selector. However, the a.externaL rule set follows the a:Link rule set. As a result, a.externaL takes precedence. Most of our links will be cadet blue, but those with cLass=”externaL” will be orange.

Complex and combinator selectors, of course, give us higher-specificity values. Consider the following CSS:

ul#story-list > .book-review {

color: #0c0;

}

#story-list > .book-review {

color: #f60;

}

Although these rule sets look similar, they aren’t the same. The first selector, uL#story-List > .bookreview , contains a type selector ( uL ), an ID selector ( #story-List), and a class selector ( .bookreview ). It has a specificity value of 1,1,1. The second selector, #story-List > .book-review , only contains an ID and a class selector. Its specificity value is 1,1,0. Even though our #story-List > .book-review rule follows uL#story-List > .bookreview , the higher specificity of the former means that those elements with a .book-review class will be green rather than orange.

Although most pseudo-classes increase the specificity of a selector, :not() and :is() work a bit differently. They don’t change the specificity value of a selector. Instead, the specificity value of these selectors gets replaced by the value of the most specific selector in their arguments. In other words, the specificity value :not( [type=text] ) is the same as [type=text] : 0,1,0.

Source: Brown Tiffany B (2021), CSS , SitePoint; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *