I'm confused as to why this example doesn't work:
CSS:
p {
color: red;
}
div:not(.exclude) p {
color: green;
}
HTML:
<div>
<div class="exclude">
<p>I should be red</p>
</div>
<div>
<p>I should be green</p>
</div>
</div>
The end result is that both <p>
are green, but I would have expected the first one to be red. Here's a JSFiddle.
Interestingly, I found three different ways to make it work:
- Remove the top-level
<div>
from the HTML
- Change the top-level
<div>
to a different element (e.g. <section>
)
- Add an extra
div
to the beginning of the second CSS selector (div div:not(.exclude) p
)
And another weird way to break it:
- Using solution 2 as a basis, wrap another
<div>
around the <section>
According to MDN:
This selector only applies to one element; you cannot use it to exclude all ancestors. For instance, body :not(table) a
will still apply to links inside of a table, since <tr>
will match with the :not()
part of the selector.
That makes sense, but I don't think that this is happening here. Since there is nothing between <div class="exclude">
and its direct child <p>
, it should trigger the rule regardless of what it is nested inside. What am I missing? I'd really appreciate if someone could help me understand this.
Since there is nothing between <div class="exclude">
and its direct child <p>
, it should trigger the rule regardless of what it is nested inside. What am I missing?
The <p>
is a descendant of both the top-level <div>
and <div class="exclude">
. So while the latter doesn't match the selector, the former does, and therefore you have a match. It doesn't matter either that the ancestor that fails to match the selector is closer to the <p>
than the one that does.
Solutions 1 and 2 work by eliminating that match altogether.
Solution 3 works when no other <div>
s exist in the <p>
's ancestry, because then you restrict your selector to those exact criteria, in that exact order. Which means if you swapped the class attribute from the inner <div>
to the outer one, it would no longer match the selector, and conversely if you swapped the class selector from the inner div
to the outer one, the selector would not match the original HTML structure (again, assuming no other <div>
s exist in the hierarchy).
Wrapping another <div>
around the <section>
just causes the selector to be matched again by that <div>
. The <section>
is ignored, in much the same way as <div class="exclude">
.
See also:
- CSS negation pseudo-class :not() for parent/ancestor elements
- Why doesn't this CSS :not() declaration filter down?
- Is the CSS :not() selector supposed to work with distant descendants?
You have an overriding <div>
even higher than <div class="exclude">
.
In your styles, you have indicated that - as far as the :not
pseudo-class is concerned - any ancestor <div>
will do. (ie. a grandparent <div>
is just as good as a parent <div>
.)
To focus on the immediate parent, your CSS needs to employ the direct child selector (>
):
p {
color: red;
}
div:not(.exclude) > p {
color: green;
}
<div>
<div class="exclude">
<p>I should be red</p>
</div>
<div>
<p>I should be green</p>
</div>
</div>
You have discovered a common pitfall in using :not
, as well described by the other answers. The classic approach in CSS to dealing with this problem, and the way we did it before CSS3 provided us with the ability to shoot ourselves in the foot with :not
, is to write several rules, one dealing with the general case, the second with the exception. In your case, this would look like:
p { color: red; }
div p { color: green; }
div.exclude p { color: red; }