Is the CSS :not() selector supposed to work with distant descendants?

Is this supposed to work like I think it should?

No, the behavior you’re seeing is correct.

In your last example, although the <blockquote> contains a <p>, it’s the <blockquote> itself that’s matching *:not(p), as well as the condition that it must be a descendant of the <div>, which it is. The style is applied only to the <blockquote>, but it is then inherited by the <p> inside it.

The <p> element itself still counts against the negation, so the <p> itself is still being excluded from your selector. It’s just inheriting the text color from its parent, the <blockquote> element.

Even if none of its relatively close ancestors matched the selector, you have elements like html and body to worry about as well — although you could probably just tack on a body selector in the very beginning:

body div...

This is why I often strongly advise against using the :not() selector for filtering descendants, especially when not qualified with a type selector (like div in your example). It doesn’t work the way most people expect it to, and the use of inherited properties like color only serves to compound the problem, on top of making it even more confusing for authors. See my answers to these other questions for more examples:

The solution to the problem described is to simply apply a different color to <p> elements. You won’t be able to simply exclude them with a selector because of inheritance:

/* Apply to div and let all its descendants inherit */
div {
  color: red;
}

/* Remove it from div p */
div p {
  color: black;
}

On Selectors Level 4: yes, :not() has indeed been enhanced to accept full complex selectors that contain combinators. Essentially, this means (once browsers begin implementing it) you will be able to write the following selector and have it do exactly what you want:

p:not(div p) {
  color: red;
}

In case anyone is interested, this works in jQuery today.

Leave a Comment