This content originally appeared on Bram.us and was authored by Bramus!
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
The ID selector #page
for example 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 value of 197121
might not tell you much, but when converted to hex it reads 0x030201
which is more useful. By masking and bitshifting, you can easily get the actual values:
s = 0x030201; // 197121
a = (s & 0xff0000) >> 16; // 3
b = (s & 0x00ff00) >> 8; // 2
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. Technically, 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.
~
🔥 Like what you see? Want to stay in the loop? Here's how:
This content originally appeared on Bram.us and was authored by Bramus!
Bramus! | Sciencx (2023-02-21T11:05:04+00:00) (255,255,255) is the Highest Specificity. Retrieved from https://www.scien.cx/2023/02/21/255255255-is-the-highest-specificity/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.