Posts from Wednesday, March 7th, 2012

Negative Proximity

Published 12 years, 1 week past

There’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.)


Browse the Archive