Tackling CSS Specificity

How often have you been frustrated because you’ve just joined a new project and can’t figure out why your styles aren’t being applied properly? Or maybe you’ve just integrated a new library, and suddenly all of your styles are being overridden. You could slap an !important declaration onto your styles and call it a day, but that would throw maintainability out the window. So you’ve finally decided to learn about CSS specificity.

Specificity is one of the foundational concepts of CSS, and understanding it allows you to write maintainable, efficient CSS. So what exactly is specificity?

What Is CSS Specificity?

CSS specificity is the algorithm browsers use to determine which style declarations should be applied to an element. Each selector is assigned a particular weight depending on its specificity and then these weights are measured against each other to determine which style rules win for a particular element. We calculate specificity using three categories: ID, class, and type. Let’s take a deeper look into these weighted selector categories.

Weighted Selectors

The ID category includes ID selectors (i.e. #about). For every ID in a selector, add 1-0-0 to the weight.

The class category includes class selectors (i.e..about), attribute selectors (i.e. [type="button"]), and pseudo-classes (i.e. :hover:checked). For every class, attribute, and pseudo-class in a selector, add 0-1-0 to the weight.

The type category includes type selectors (i.e. h3aside) and pseudo-elements (i.e. ::after). For every type and pseudo-element in a selector, add 0-0-1 to the weight.

Calculating Specificity

For each of the categories above, we add one to each respective column when we use a selector of that type. For example, if a selector contains a type followed by an ID, we would add a 1 in the ID column and a 1 in the type column giving us a specificity of 1-0-1.

/* 1-0-1 */
aside #paragraph {
  ...
}
Code language: CSS (css)

To calculate which selector is more specific, we compare the values found in each column. For example, let’s say we have two selectors, each styling a paragraph element.

.paragraph {
  ...
}

.aside p {
  ...
}
Code language: CSS (css)

The first selector contains only one class, so its value is 0-1-0.

The second selector contains a class selector and a type selector, so its value is 0-1-1.

Now we can compare the two selectors column by column. Starting with the most specific column, the ID selectors column, both selectors have a 0, so we can move on to the next column.

In the classes, pseudo-classes, and attribute selectors column, each selector has a 1. Since these values are equal, we must compare the third column to determine which selector wins. In the final column, type selectors and pseudo-elements, only the second selector has a 1, so it is more specific.

Examples of Calculating Specificity

Let’s look at a few more examples and see if we’re comfortable before moving on.

In the first example, we have three selectors. These three selectors are all attempting to change the color of a paragraph’s text. The first selector uses two class selectors; the second selector uses a class selector followed by a type selector. The third selector uses two type selectors.

.my-aside .my-paragraph {
  color: blue;
}

.my-aside p {
  color: red;
}

aside p {
  color: yellow;
}
Code language: CSS (css)

Let’s evaluate each of these selectors using the three-column method.

  1. The first selector .my-aside .my-paragraph contains two classes, so it gets a two in the second column.
  2. The second selector .my-aside p includes a class and a type selector, so it gets one in the second and third columns.
  3. The last selector aside p contains two types, so it gets a two in the third column.

Starting from column one, none of the selectors have an ID, so we move on to column two. In column two, we see that the first selector contains two classes while the other selectors contain one and zero, respectively. Thus, selector one is the most specific, and we don’t need to examine column three (assuming all three selectors are styling the same CSS property).

calculating specificity, example 3

The first example was pretty clear cut, but what if we have two selectors of equal weight attempting to style the same element?

<style>
  .aside .paragraph {
    color: blue;
  }

  .my-aside .my-paragraph {
    color: red;
  }
</style>

<aside class="aside my-aside">
  <p class="paragraph my-paragraph">...</p>
</aside>
Code language: HTML, XML (xml)

We can see here that both selectors attempt to style the same paragraph element and have a specificity of 0-2-0. So which color will the paragraph text be?

The text will be red because the latest CSS declaration wins. When two selectors of equal weight attempt to style the same CSS property, the latest, or most recent, declaration wins.

If you want the text to be blue, you’ll have to increase the specificity of the first selector or decrease the specificity of the second selector. You could also change the order of the declarations so the first selector becomes the latest selector.

Unweighted Selectors

In the previous section we took a look at weighted selectors, or selectors that contribute to a selector’s specificity. There are, however, some selectors which are unweighted and do not contribute to a selector’s specificity weight.

These selectors include:

  • The universal selector (*)
  • The :where() pseudo-class
  • Combinators (i.e. +, >)
  • The :is() pseudo-class (the selectors inside the parentheses are weighted)
  • The :not() pseudo-class (the selectors inside the parentheses are weighted)

Let’s take a look at a few examples containing unweighted selectors.

Example of Unweighted Selectors

Here we have three sections that contain paragraphs of text. The second section has a class of highlight.

In our CSS, the first selector selects all paragraphs that are direct children of elements containing a class of highlight.

The second selector selects all paragraphs that are descendants of section elements that do not contain the class highlight.

<style>
  .highlight > p {
    background: yellow;
  }

  section:not(.highlight) p {
    background: blue;
    color: white;
  }
</style>

<section>
  <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Minus modi 
    quasi in nisi nobis! Accusamus possimus perferendis consectetur libero 
    ut dolorem eveniet officiis aliquam. Accusantium cumque ab ratione
    exercitationem perspiciatis!</p>
</section>

<section class="highlight">
  <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Minus modi 
    quasi in nisi nobis! Accusamus possimus perferendis consectetur libero 
    ut dolorem eveniet officiis aliquam. Accusantium cumque ab ratione
    exercitationem perspiciatis!</p>
</section>

<section>
  <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Minus modi 
    quasi in nisi nobis! Accusamus possimus perferendis consectetur libero 
    ut dolorem eveniet officiis aliquam. Accusantium cumque ab ratione
    exercitationem perspiciatis!</p>
</section>
Code language: HTML, XML (xml)

The UI for this code looks like this. The first and third paragraphs have a blue background and white text (per selector two) and the middle paragraph, which is contained within a section with a class of highlight, has a background color of yellow per selector one.

If we take a look at the specificities for each selector we see that selector one has a specificity of 0-1-1 and selector two has a specificity of 0-1-2. Notice that the direct child combinator (>) and the negation pseudo-class (:not()) do not contribute to the weight of the specificity, even though the selector inside of the negation pseudo-class does contribute.

Inline Styles

Inline styles are added to elements via the style attribute (i.e. style="color: blue") and override any author-defined styles. The only styles that override inline styles use the !important declaration (which we’ll discuss in the next section).

For code maintainability it’s recommended to encapsulate your styles in external stylesheets (linked with <link rel="stylesheet" href="..." />) and don’t use inline styles unless absolutely necessary.

Example of Inline Styles

In the example below, we’ve declared a style that should turn all elements with a class of paragraph blue. However, this style is being overridden on the first paragraph by an inline style declaration which sets the paragraph color to red. The second paragraph text will still be blue.

<style>
  .paragraph {
    color: blue;
  }
</style>

<p class="paragraph" style="color: red;">lorem ipsem</p>
<p class="paragraph">lorem ipsem</p>
Code language: HTML, XML (xml)

!important

Adding !important to the end of a style is a way for the code author to give a CSS value more weight than it has by default.

If two conflicting styles contain !important declarations, the higher-specificity declaration wins. If both elements have the same weight, the last declaration wins.

Unfortunately, using !important has become an “easy solution” for many developers, leading to specificity chaos and unmaintainable code. !important should not be used unless absolutely necessary. The code becomes more difficult to maintain and disrupts the natural flow of style calculations by giving more weight to selectors which don’t necessarily deserve that weight.

!important is a great tool for testing, aiding accessibility, or temporarily fixing an urgent problem, but it should not be considered a long-term solution.

Let’s take a look at an example of how !important impacts style.

Example of !important

Here we have an unordered list with three list items, all wrapped in a div. On line two we select all list items that are direct children of an unordered list. On line 6 (div li) we select all list items contained within a <div>.

Both of these selectors have equal weights (remember the direct descendent combinator, <, is unweighted) of 0-0-2, and are attempting to style the same CSS property. Under normal circumstances the selector div li would win and the list item text would be red, however we’ve added an !important declaration to the style rule on line 3 (color: blue !important;), which overrides the style declared on line 7 (color: red;). Thus, the list item text is blue.

<style>
  ul > li {
    color: blue !important;
  }

  div li {
    color: red;
  }
</style>

<div>
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
  </ul>
</div>
Code language: HTML, XML (xml)

Specificity Challenges

Now that you’ve learned the basics of CSS specificity, it’s time to put it to the test. See if you can determine what the UI will look like based on the following code snippets. Answers are provided at the end of this post.

Specificity Challenge 1

<style>
  * {
    font-family: `Arial`, sans-serif;
  }

  section {
    margin-bottom: 24px;
    border: 2px dotted black;
    padding: 16px;
  }

  button {
    background: skyblue;
    border: none;
    padding: 12px;
  }

  p {
    font-family: 'Tahoma';
  }
  
  .content {
    font-family: 'Georgia';
  }
  
  #section-1-read-more {
    background: orange;
  }
</style>

<section>
  <h1 class="title">Section 1</h1>
  <p class="content">Lorem ipsum dolor sit amet consectetur adipisicing elit. 
    Velit maiores...</p>
  <p class="content">Lorem ipsum dolor sit amet consectetur adipisicing elit. 
    Velit maiores...</p>
  <button id="section-1-read-more">Read more</button>
</section>

<section>
  <h1 class="title">Section 2</h1>
  <p class="content">Lorem ipsum dolor sit amet consectetur adipisicing elit. 
    Velit maiores...</p>
  <p class="content">Lorem ipsum dolor sit amet consectetur adipisicing elit. 
    Velit maiores...</p>
  <button id="section-2-read-more">Read more</button>
</section>
Code language: HTML, XML (xml)

Specificity Challenge 1 Solution

There will be two sections. Each section will have a bottom margin of 24px, a 2px dotted border and 16px padding on all sides.

Each section header will have a font-family of Arial or another sans-serif font as declared by the universal selector on line 1.

Each paragraph will have a font family of Georgia because of the content class selector on line 22 .content (it overrides the type selector on line 18 p).

Each button will have a padding of 12px on all sides and will not have a border. Only button two will have a background color of skyblue (defined by the type selector on line 12 button). The first button will have a background color of orange as defined by the ID selector on line 27 #section-1-read-more.

<style>
  * { /* 0-0-0 */
    font-family: 'Arial', sans-serif;
  }

  section { /* 0-0-1 */ 
    margin-bottom: 24px; 
    border: 2px dotted black; 
    padding: 16px;
  }

  button { /* 0-0-1 */ 
    background: skyblue; 
    border: none; 
    padding: 12px;
  }
  
  p { /* 0-0-1 */
    font-family: 'Tahoma';
  }
  
  .content { /* 0-1-0 */
    font-family: 'Georgia';
  }
  
  #section-1-read-more { /* 1-0-0 */
    background: orange;
  }
</style>
... 
Code language: HTML, XML (xml)

Specificity Challenge 2

<style>
  li > a {
    color: pink;
    text-decoration: none;
  }
  
  a.link {
    color: orange;
  }

  a[visited="true"] {
    text-decoration: underline;
  }
  
  li:first-of-type a {
    color: blue;
  }
</style>
<ul>
  <li><a class="link" href="#">Item 1</a><li>
  <li><a class="link" href="#" visited="true">Item 2</a>
  <li><a class="link" href="#">Item 3</a></li>
</ul>
Code language: HTML, XML (xml)

Specificity Challenge 2 Solution

There will be an unordered list with three items: item 1, item 2, and item 3. Item 1 will be blue because of the :first-of-type pseudo-class selector on line 15. Items 2 and 3 will be orange. Item 2 will be underlined.

<style>
  li > a { /* 0-0-2 */
    color: pink;
    text-decoration: none;
  }
  
  a.link { /* 0-1-1 */ 
    color: orange;
  }
  
  a[visited="true"] { /* 0-1-1 */
    text-decoration: underline;
  }
  
  li:first-of-type a { /* 0-1-2 */
    color: blue;
  }
</style>
...
Code language: HTML, XML (xml)

Specificity Challenge 3

<style>
  input[type="text"] {
    border: 2px solid red;
  }
  
  input {
    border: 4px solid purple;
  }
  
  input#first-name {
    border: 6px solid orange;
  }
  
  #first-name {
    border: 8px solid black;
  }
</style>
<label for="first-name">First name</label>
<input id="first-name" type="text">
Code language: HTML, XML (xml)

Specificity Challenge 3 Solution

There will be a label (First name) and a text input. The text input will have a 6px solid orange border due to the type/ID selector on line 10 input#first-name.

<style>
  input[type="text"] { /* 0-1-1 */
    border: 2px solid red;
  }
  
  input { /* 0-0-1 */
    border: 4px solid purple;
  }
  
  input#first-name { /* 1-0-1 */
    border: 6px solid orange;
  }
  
  #first-name { /* 1-0-0 */
    border: 8px solid black;
  }
</style>
<label for="first-name">First name</label>
<input id="first-name" type="text">
Code language: HTML, XML (xml)

You Learned Specificity!

CSS specificity is tricky, but once you get the gist, it will become second nature. As one of the foundational elements of CSS, having a solid grasp of specificity will allow you to work confidently with legacy code bases and third-party libraries.

If you want to go further with CSS specificity, here are a few resources for you to check out!

More Resources on CSS Specificity

Leave a Reply

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