Skip to: site navigation/presentation
Skip to: Thoughts From Eric

Negative Proximity

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;} p {font-weight: normal;}

<div class="one">
  <div class="two">
    <p>Hi there!</p>

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;} p {background: red;}

<div class="one">
  <div class="two">
    <p>Hi there!</p>

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

Eight Responses»

    • #1
    • Comment
    • Wed 7 Mar 2012
    • 1618
    Aubrey wrote in to say...

    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.

    • #2
    • Comment
    • Wed 7 Mar 2012
    • 2023
    Eric Meyer wrote in to say...

    Whoops! Indeed so, Aubrey. I’ve corrected the error. Thanks!

    • #3
    • Comment
    • Thu 8 Mar 2012
    • 1514
    Stephanie Hobson wrote in to say...

    Silly computers doing what we say instead of what we mean.

    • #4
    • Comment
    • Sat 10 Mar 2012
    • 1730
    jftf wrote in to say...

    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.

    • #5
    • Pingback
    • Tue 13 Mar 2012
    • 1641
    Received from Some links for light reading (14/3/12) | Max Design

    […] Negative Proximity […]

    • #6
    • Comment
    • Wed 14 Mar 2012
    • 1222
    Niels Matthijs wrote in to say...

    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.

    • #7
    • Pingback
    • Fri 16 Mar 2012
    • 0909
    Received from 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 […]

    • #8
    • Comment
    • Tue 29 May 2012
    • 1519
    Paceaux wrote in to say...

    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{
    :not(.ie) #main a{

    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?

Leave a Comment

Line and paragraph breaks automatic, e-mail address required but never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Remember to encode character entities if you're posting markup examples! Management reserves the right to edit or remove any comment—especially those that are abusive, irrelevant to the topic at hand, or made by anonymous posters—although honestly, most edits are a matter of fixing mangled markup. Thus the note about encoding your entities. If you're satisfied with what you've written, then go ahead...

March 2012
February April