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

2

8

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?

Schexnayder answered 1/1, 2014 at 13:21 Comment(0)
O
12

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.

Oppidan answered 1/1, 2014 at 13:28 Comment(4)
Thanks for the links. As I said, I searched but I didn't find these questions. It's difficult to search for "not" as we get a lot of irrelevant results.Schexnayder
+1. Thanks for the complete answer, and for your edit following mine! ;) However, I am still perplexed by what the standard is saying. I re-read the CSS3 specs and I don't see that it says anything about grand-children nor about only applying the rule to a direct child. BoltClock's comment below my question is revealing. What would the difference be between div > *:not(p) and div *:not(p)? Isn't it a matter of interpretation?Schexnayder
@augustin: The :not() selector ignores combinators - it only operates on the element being matched and doesn't check if it is a direct or distant descendant of another element. In the same way that div p.test would match a superset of the elements that div > p.test would match (i.e. p.test elements), div *:not(p) would also match a superset of elements matched by div > *:not(p) (i.e. elements that are themselves not p).Oppidan
@augustin: Or, to summarize, the :not() selector acts just like any other pseudo-class or a class selector or an ID selector.Oppidan
U
3

The color is assigned to the blockquote, and is then inherited by the p.

:not(p) just makes it so that the styles are not directly applied. They are still inherited though.

Ustkamenogorsk answered 1/1, 2014 at 13:28 Comment(1)
+1. It took me a while but I finally understand what you mean! ;) Thanks a lot and happy New Year!Schexnayder

© 2022 - 2024 — McMap. All rights reserved.