# It’s all about that base
CSS Specificity is expressed as a tuple/triad/triple (A,B,C)
.
- Count the number of ID selectors in the selector (=
A
)- Count the number of class selectors, attributes 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
For example, the ID selector #page
has a specificity of (1,0,0)
While goofing around in the Chromium Source to see how it’s stored there, I found this interesting piece of code about it:
// We use 256 as the base of the specificity number system.
unsigned Specificity() const;
As the code snippet shows, Blink –Chromium’s underlying rendering engine– stores Specificity as a single number using base 256
. This is OK, and is something that used to be mentioned in an older version of the spec:
Concatenating the three numbers a-b-c (in a number system with a large base) gives the specificity.
Now, the choice for 256
seems OK, as it’s quite a large base and also allows for easy value extraction when representing the value in hex. A decimal value of 197121
might not tell you much, but when converted to hex it reads 0x030201
which is more useful. By bit masking and bit shifting, you can easily get the individual values for the A, B, and C components of the Specificity.
unsigned s = 197121; // 0x030201 in hex
uint8_t a = (s & 0xff0000) >> 16; // 3
uint8_t b = (s & 0x00ff00) >> 8; // 2
uint8_t c = (s & 0x0000ff); // 1
So 197121
represents a specificity of (3,2,1)
🤔 Need help calculating Specificity? In my talk The CSS Cascade: A Deep Dive I tell you all about it. Fast forward to the 13 minute mark of the recording.
The Polypane Specificity Calculator is a tool I would also like to recommend. If you want to work with specificity in your own code, go check out @bramus/specificity
which I created. Pass in a selector and out comes the specificity.
~
# Exceeding the base
With this base of 256
I wondered: what happens to the Specificity if you exceed the base number? As Kilian Valhof detailed before this can cause problems, because values would overflow into the other component.
For example, a decimal value of 256
represented in hex equals 0x000100
. With a base of 256
and using the masking and shifting logic above, that would result in a Specificity of (0,1,0)
instead of (0,0,256)
… which is not good. It would essentially mean that a selector with a specificity of (0,0,256)
would that somehow equal a (0,1,0)
selector.
Thankfully this is not the case. When fabricating a selector that has a specificity of (0,0,256)
using :not()
, we can see it does not beat a (0,1,0)
selector
/* (0,1,0) */
.special {
color: hotpink;
}
/* (0,0,256) */
p:not(a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a) {
color: blue;
}
See the Pen Specificity (0,0,256) vs (0,1,0) by Bramus (@bramus)on CodePen.
Testing the snippet above the .special
will be painted hotpink
, so it’s definitely not overflowing. Phew!
~
# Hitting a ceiling
So, what does happen when Blink needs to store a Specificity component of 256
? Looking at the source, we can see that Blink clamps the values.
unsigned CSSSelector::Specificity() const {
// make sure the result doesn't overflow
static const unsigned kMaxValueMask = 0xffffff;
static const unsigned kIdMask = 0xff0000;
static const unsigned kClassMask = 0x00ff00;
static const unsigned kElementMask = 0x0000ff;
if (IsForPage()) {
return SpecificityForPage() & kMaxValueMask;
}
unsigned total = 0;
unsigned temp = 0;
for (const CSSSelector* selector = this; selector; selector = selector->TagHistory()) {
temp = total + selector->SpecificityForOneSelector();
// Clamp each component to its max in the case of overflow.
if ((temp & kIdMask) < (total & kIdMask)) {
total |= kIdMask;
} else if ((temp & kClassMask) < (total & kClassMask)) {
total |= kClassMask;
} else if ((temp & kElementMask) < (total & kElementMask)) {
total |= kElementMask;
} else {
total = temp;
}
}
return total;
}
Because of this, each component of the Specificity is limited to a maximum value of 0xFF
(255
in decimal). This can be confirmed by creating a page that has selectors with specificity of (0,0,256)
and (0,0,255)
.
/* (0,0,257) ~> (0,0,255) */
p:not(a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a) {
color: red;
}
/* (0,0,256) ~> (0,0,255) */
p:not(a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a) {
color: purple;
}
/* (0,0,255) */
p:not(a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a) {
color: blue;
}
With their specificity clamped to (0,0,255)
, the cascade will fall back to order of appearance, so the paragraph will be blue
.
See the Pen Specificity: (0,0,257) vs (0,0,256) vs (0,0,255) vs (0,0,254) by Bramus (@bramus) on CodePen.
This clamping of values wasn’t always the case. Before this commit authored on 2012-10-04 (Chromium, WebKit) it was possible to have 256 element selectors beat 1 class selector 😱
~
# What about other engines?
Checking the page above in Safari yields the same result. WebKit –with which Blink shares its history– also stores Specificity as a number using base 256
. Firefox with its Servo engine on the other hand does not seem to be affected by it. Turning to its source code, we can see it stores Specificity as an unsigned 32 bit integer and uses a 10 bit mask which has a max value of 1024
.
const MAX_10BIT: u32 = (1u32 << 10) - 1;
impl From<u32> for Specificity {
#[inline]
fn from(value: u32) -> Specificity {
assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT);
Specificity {
id_selectors: value >> 20,
class_like_selectors: (value >> 10) & MAX_10BIT,
element_selectors: value & MAX_10BIT,
}
}
}
Checking this demo in Firefox, you’ll see a blue
paragraph, confirming the max specificity there is (1023,1023,1023)
~
# So, what does this all mean?
Nothing much, really. By design CSS Specificity is unlimited. But every system has its limits, and for CSS Specificity in Blink and WebKit that limit is 255
. For Servo it is 1023
.
In practice you’ll never hit that limit and should you do, I urge you to stop writing bad CSS selectors like that.
~
# Spread the word
To help spread the contents of this post, feel free to retweet its announcement tweet:
(255,255,255) is the Highest Specificity
🏷 #css #specificity pic.twitter.com/QdUBDAOxoj
— Bram.us (@bramusblog) February 21, 2023
~
🔥 Like what you see? Want to stay in the loop? Here's how:
I remember testing some absolutely stupid edge cases a few years back. I also published some of my findings that you can find interesting.
https://pawelgrzybek.com/css-specificity-explained/#interesting-facts-about-specificity