CSS specificity: it ain't as hard as its pronunciation

CSS specificity: a concept every front end developer has been struggling with at some point. It‘s usually the reason why your CSS rules don’t apply to some elements, although you explicitly defined it.

Many developers tend to learn CSS specificity by trial-and-error, which often results in the overuse of styling with id’s or with the well-known last resort !important. In most cases these issues can easily be solved by tweaking your selectors so that your code will remain clear.

 

Overview

  • Specificity determines which CSS rules are applied.
  • If multiple selectors apply to the same element, the one with the highest specificity will be applied.
  • If multiple selectors with the same specificity apply to the same element, the selector which is defined last will be applied.
  • Some properties (like color and other font-related stuff) are inherited from its parent by default, in case no selectors target this property.
  • There are 4 categories which define the specificity level of a given selector:
    • inline styles
    • ID's
    • classes, attributes & pseudo-classes
    • elements & pseudo-elements.
  • There are several notations for CSS specifity, we‘re going to use the 0,0,0,0-notation. Every zero stands for the specificity in the categories mentioned above.

 

Terminology

  • An inline style is the markup set in the HTML-markup, in the style-attribute <span style="color: red;">Lorem Ipsum</span>
  • An ID selector is the selector that uses the hashtag (#) to target a unique element with a certain id in the DOM #header, #main-content
  • A class selector uses the dot (.) to target elements with certain class .container, .field
  • An attribute selector uses square brackets ([]) to target elements with a certain attribute. [href], [id="header"], [href$="https://"]
  • A pseudo-class selector is used to define a special state of an element. They can be recognised by the colon (:) :hover, :disabled
  • An element selector is the name of the type of element in the DOM. div, span, br
  • A pseudo-element is used to style specified parts of an element. They can be recognised by the double colon (::). Watch out: double colons were introduced in the CSS3 spec, in CSS1 & CSS2 the single colon was used. The old syntax is still widely used for backwards compatibility. ::after, ::selection

 

Specificity measurement

Let’s start with this example:

body.logged-in #header img.logo:hover

In this selector there is 1 id (#header), 2 classes (.logged-in & .logo) + 1 pseudo-class (:hover) and 2 elements (body & img) and this results in a specificity of (0,1,3,2). The first parameter is 0 as long as there are no inline styles applied.

 

Some more examples

Example 1

div{}

Specificity: 0,0,0,1

  • 1 element

Example 2

a:hover{}

Specificity: 0,0,1,1

  • 1 element
  • 1 pseudo class

Example 3

.container{}

Specificity: 0,0,1,0

  • 1 class

Example 4

ul > li{}

Specificity: 0,0,0,2

  • 2 elements

combinators like >, + or ~ don’t influence specificity

Example 5

@media media and (max-width: 900px){
  a[href]:after{
    content: "(" attr(href) ")";
  }
}

Specificity: 0,0,2,1

  • 1 attribute ([href]) + 1 pseudo class (:after)
  • 1 element (a).

Mediaqueries don’t influence specificity.

Example 6

*

Specificity: 0,0,0,0

The universal selector doesn’t influence specificity

Example 7

* + *

Specificity: 0,0,0,0

Example 8

.views-row:not(:last-child)

Specificity: 0,0,2,0

  • 1 class (.views-row) + 1 pseudo class (:last-child)

The :not() selector doesn’t influence specificity, but the selector between the brackets of the :not() does

Example 9

html > body #main .container:not(.main-content)

Specificity: 0,1,2,2

  • 1 ID (#main)
  • 2 classes (.content & .main-content)
  • 2 elements (html & body)

Example 10

#content input[type="checkbox"]:disabled ~ :not(.♥) > span:only-of-type + #cta:empty::after

Specificity: 0,2,5,3

  • 2 ID’s (#content & #cta)
  • 1 class (.♥) + 3 pseudo classes (:disabled, :only-of-type, :empty) + 1 attribute ([type="checkbox"])
  • 2 elements (input & span) + 1 pseudo-element (::after)

Note: if you're writing code like this, you're probably doing it wrong. In case you're wondering on which element this selector applies, check out the demo here.

Example 11

<span style="color: blue;">Foo</span>

Specificity: 1,0,0,0

 

Who wins the battle?

specificity_schema.png

Specificity can not be overridden if you’re using selectors from a category with lower specificity. Inline styles will override IDs, IDs will override classes and so on.

0,0,2,1 > 0,0,0,1
1,0,0,0 < 1,0,0,1
0,6,0,5 < 1,0,0,0
0,0,2,1 > 0,0,0,1

 

Demotime

Higher specificity wins

Parent specificity doesn’t override own specificity

Even if the specificity of the parent is higher.

Inheritance

Some properties are inherited from the closest parent by default. In this case, the span element inherits its color from .inner, although .outer is defined later.

Tree proximity ignorance

If the element itself is targeted, it doesn’t matter how close a parent in the selector is; the selector with the highest specificity will be applied (in this case they have the same specificity, so the last selector will be applied).

The universal selector doesn’t influence specificity

Even though multiple universal selectors are involved.

<style> vs <link rel="stylesheet">

Specificity doesn’t get influenced if you're using CSS in a style element or link to a CSS-file with the link element. If a selector in a style element and in a CSS-file have the same specificity, the one defined last in the DOM will be applied. In this example the color will be overridden by the linked CSS-file and the background from the CSS-file will be overridden by the last style-element.

!important to remember

!important will always override a property. If there are multiple !important’s, the property of the selector with the highest specificity will be applied.

Animations

Some properties can be overridden by animations, even when !important is set. You can find an overview of the animatible properties here on W3Schools.

 

Tips for writing selectors

  • Calculating the specificity value while developing can help writing clean code.
  • If you're not supporting legacy browsers like IE8, use the double colon for pseudo-elements to increase consistency.
  • Use selectors of the classes, pseudo-classes and attributes group. ID's will drasticly increase specificity which can result in the overuse of !important.
  • Be carefull with selectors like .site-header .container{}. It's better to add a class to the .container-element with a relevant meaning.
  • Only use !important under the following circumstances:
    • Overriding 3rd party code & inline styles set by js-libraries.
    • Utility classes (classes like .hidden or .pull-left)
  • An easy and performant way of increasing the specificity of a selector is by doubling a selector. If the specificity of .contextual-links a is too low, try .contextual-links.contextual-links a.
  • KISS! The best selectors are the short ones. Try to remain under 4 subselectors per selector.
  • Methodologies like BEM can drasticaly improve the simplicity of your code.

Meer weten? Contacteer ons!