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

css, css-selectors

Solution

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:

- Why doesn't this CSS :not() declaration filter down?

- CSS negation pseudo-class :not() for parent/ancestor elements

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.

Problem

Here is the official documentation for the CSS3 `:not()` pseudo-class: http://www.w3.org/TR/css3-selectors/#negation and the proposed CSS Selectors Level 4 enhancement: http://dev.w3.org/csswg/selectors4/#negation I've been searching the implementation and browser support for `:not()`, but the only examples I found were with a single element or with a direct child of an element, e.g.: ``` div *:not(p) { color: red; } ``` The example above works when `<p>` is a direct child of `<div>`, but it does not work when `<p>` is a more distant descendant of `<div>`. ``` div :not(p) { color: red; } ``` ``` <div> <ul> <li>This is red</li> </ul> <p>This is NOT</p> <blockquote><p>This is red but is not supposed to be!</p></blockquote> </div> ``` If the answer is in the official documentation above, then I didn't find/understand it. As I said, I have searched this site and the web but couldn't find any discussion about the support or lack thereof of :not() as grand-children of another element. Is this supposed to work like I think it should?

Original source

Related problems