Negative Proximity
Published 12 years, 8 months pastThere’s a subtle aspect of CSS descendant selectors that most people won’t have noticed because it rarely comes up: selectors have no notion of element proximity. Here’s the classic demonstration of this principle:
body h1 {color: red;} html h1 {color: green;}
Given those styles, all h1
elements will be green, not red. That’s because the selectors have equal specificity, so the last one wins. The fact that the body
element is “closer to” the h1
than the html
element in the document tree is irrelevant. CSS has no mechanism for measuring proximity within the tree, and if I had to place a bet on the topic I’d bet that it never will.
I bring this up because it can get you into trouble when you’re using the negation pseudo-class. Consider:
div:not(.one) p {font-weight: bold;} div.one p {font-weight: normal;} <div class="one"> <div class="two"> <p>Hi there!</p> </div> </div>
Given these styles, the paragraph will not be boldfaced. That’s because both rules match, so the last one wins. The paragraph will be normal-weight.
“AHA!” you cry. “But the first rule has a higher specificity, so it wins regardless of the order they’re written in!” You’d think so, wouldn’t you? But it turns out that the negation pseudo-class isn’t counted as a pseudo-class. It, like the univseral selector, doesn’t contribute to specificity at all:
Selectors inside the negation pseudo-class are counted like any other, but the negation itself does not count as a pseudo-class.
— Selectors Level 3, section 9: Calculating a selector’s specificity
If you swapped the order of the rules, you’d get a boldfaced paragraph thanks to the “all-other-things-being-equal-the-last-rule-wins” step in the cascade. However, that wouldn’t keep you from getting a red-on-red paragraph in this case:
div:not(.one) p {color: red;} div.one p {background: red;} <div class="one"> <div class="two"> <p>Hi there!</p> </div> </div>
The paragraph is a child of a div
that doesn’t have a class
of one
, but it’s also descended from a div
that has a class
of one
. Both rules apply.
(Thanks to Stephanie Hobson for first bringing this to my attention.)
Comments (9)
Do you mean:
The paragraph is a child of a div that doesn’t have a class of one, but it’s also descended from a DIV that has a class of one. Both rules apply.
Whoops! Indeed so, Aubrey. I’ve corrected the error. Thanks!
Silly computers doing what we say instead of what we mean.
This is good stuff. Just want to point out that :not is a CSS3 pseudo selector, which isn’t available natively on older browsers, such as IE 7-8. Le sigh.
The good news is that most of us are using a Javascript framework along our CSS! Which means that $(x).not(‘.blahClass’) will work across all platforms. Yay.
Pingback ::
Some links for light reading (14/3/12) | Max Design
[…] Negative Proximity […]
Nice article! Wrote a very similar one almost two years ago (css specificity and proximity) but many people still fail to understand this. Part of the problem is probably that the space operator is equally misunderstood.
Pingback ::
Friday Focus 03/16/12: Wide Slides | Devlounge
[…] – Negative Proximity “There’s a subtle aspect of CSS descendant selectors that most people won’t have noticed […]
Thank you for writing this article. I’ve started doing some dev work with the :not() selector and I was incorrectly assuming that it added specificity. Thankfully the scenarios that I had didn’t require an original rule, but it’s good to know that the following two lines have equal specificity.
.ie #main a{
display:inline;
}
:not(.ie) #main a{
display:inline-block;
}
Why doesn’t
:not()
have specificity, then? I can only guess that it’s because there’s an assumption that you don’t have a positive rule that need to be overwritten, right?Pingback ::
Getting to Know CSS3 Selectors: Other Pseudo-Classes
[…] Negative Proximity […]