---
title: "How does CSS work? The specificity"
tags: ["css", "webdev"]
createdAt: 2023-01-30
lastUpdated: 2023-01-30
sidebar:
order: 4
image: /src/assets/clf2gzevwp3aizl2o323.png
description: "When writing CSS selectors, they sometimes don’t seem to be following the order of the code. Indeed they don’t: they also follow the specificity of each selectors. But what is the specificity and how does it work?"
---
import Codepen from "@components/Codepen.astro";
## Introduction
Let’s take this piece of code:
```html
Hello world
```
What is the color of the text? Let’s try it out:
Congratulations if you picked blue!
The reason why the color appears blue is because the selector was defined last.
---
Now let’s try the same thing with:
```html
Hello world
```
What is the color of the text now? Let’s try it out:
It appears green. But why so?
## Specificity
### General rule
CSS uses multiple rules to determine how to apply styles and selectors to elements. One of them is the **specificity**: this is a score computed for each selector. Once all of them are computed, CSS will pick the selector with the **highest** specificity.
Let’s circle back to our 2nd example. If we display the specificity of each selectors, we have:
```css
#id {
color: green;
} /* Specificity: (1, 0, 0) → WINS */
.red {
color: red;
} /* Specificity: (0, 1, 0) */
div {
color: blue;
} /* Specificity: (0, 0, 1) */
```
Which explains why the text appears `green`: `#id` has the highest specificity.
### Egality
If 2 selectors have the same specificity, then the last defined will take over, which explains the situation in our 1st example:
```css
.red {
color: red;
} /* Specificity: (0, 1, 0) */
.blue {
color: blue;
} /* Specificity: (0, 1, 0) & last one → WINS */
```
## How to compute the specificity
## General rules
The specificity is a list of 3 elements: `(A, B, C)`, where:
1. `A` is the ID selectors (e.g., `#example`)
2. `B` is the class selectors (e.g., `.example`), attributes selectors (e.g., `[type="radio"]`) and pseudo-classes (e.g., `:hover`)
3. `C` is the type selectors (e.g., `h1`) and pseudo-elements (e.g., `::before`)
> If you want to check the differences between pseudo-classes (like `:hover`) and pseudo-elements (like `::before`), you can check this post: https://dev.to/ayc0/css-before-vs-before-1d35.
### Edge cases
- [`*`](https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors) (universal selector) and [`:where()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:where) have **no specificity** `(0, 0, 0)`.
- `:is(…)`, `:not(…)`, and `:has(…)` pseudo-classes have the specificity of their most specific complex selector in their selector list argument.
For instance: `:not(.class)` will have the same specificity as `.class` (aka `(0, 1, 0)`)
- `:nth-child(…)` and `:nth-last-child(…)` have the specificity of their most specific complex selector in their selector list argument **plus** their own specificity
For instance: `:nth-child(2n of .class)` will have the same specificity as `.class` + `:nth-child` (aka `(0, 1, 0) + (0, 1, 0) = (0, 2, 0)`).
### Examples
- `.a.b.c` -> `(0, 3, 0)` (3 classes)
- `div#id` -> `(1, 0, 1)` (1 id + 1 element)
- `html` -> `(0, 0, 1)` | `:root` -> `(0, 1, 0)` (which in https://dev.to/ayc0/lightdark-mode-corrections-5e19#-raw-root-endraw-with-class-names I said that they both represent the same element with just different specificity
- `span > a[href$=".org"]:not([disabled])` -> `(0, 2, 2)` (2 elements (span & a) & 2 attributes selectors (href & not disabled)
### Combining selectors
When writing CSS rules, multiple selectors can be passed:
```css
[type="password"],
input:focus,
:root #myApp input:required {
color: blue;
}
```
In those cases, it won’t create 1 giant agglomerated selector. Instead CSS will treat those as 3 distinct rules:
```css
[type="password"] {
color: blue;
}
input:focus {
color: blue;
}
:root #myApp input:required {
color: blue;
}
```
And then it’ll continue as normal: find the selector with the highest specificity and apply it.
> If you want to know how to bump or increase the specificity of a selector, I’ll shortly release a separate article about it.
### Inline style
There are 2 ways of injecting styles in HTML:
- CSS style sheet that use selectors with specificity,
- inline style in HTML.
Inline style doesn't use selectors, and thus cannot have specificity. So how do they fit in this system?
We can think about it as a 4th element in the list `I`: `(I, A, B, C)`, that has more importance than the 3 others we’ve seen previously.
With this logic, in this example, "Hello World" should appear purple:
```html
Hello world
```
### `!important`
Even if `!important` has nothing to do with specificity, it allows styles to be applied no matter what their specificity.
Which means that in this example: even if `div` has the lowest specificity, its style will be applied and the text will appear blue:
```html
Hello world
```
Another article dedicated to how `!important` works will be released in the future.
### Tools
#### Parsel
When selectors become too complex, you can use [Parsel](https://projects.verou.me/parsel/), a library built by [Lea Verou](https://lea.verou.me/):
![Parsel parsing "#foo > .bar + div.k1.k2 [id='baz']:nth-child(2n of .k3):not(:where(#yolo))::before" and determining its specificity (1,6,2)](@assets/umseei3lx99qfkg9ip65.png)
#### VSCode
Similarly, VSCode can parse selectors and display their specificity on hover.
![VSCode parsing "#foo > .bar + div.k1.k2 [id='baz']:nth-child(2n):not(:where(#yolo))::before"](@assets/0inob8t4o2jow1j5rwc2.png)