Imagine that the HTML and CSS below is already set.
What CSS rules can I add beneath the already-written CSS to make the red paragraphs display as red?
body,
div {
display: flex;
flex-wrap: wrap;
}
p {
margin: 6px;
}
.one-filter-one p,
p[class^="one-filter-one"] {
color: blue;
}
.two-filter-two p,
p[class^="two-filter-two"] {
color: green;
}
.four-filter-four p,
p[class^="four-filter-four"] {
color: orange;
}
<p class="another-class">This is red.</p>
<p>This is red.</p>
<div class="one-filter-one">
<p>This is blue.</p>
<p class="one-filter-one--paragraph">This is blue.</p>
</div>
<p class="two-filter-two">This is green.</p>
<p>This is red.</p>
<p class="another-class-two">This is red.</p>
<div class="three-filter-three">
<p>This is unstyled (black).</p>
<div><p>This is unstyled (black) too.</p></div>
</div>
<div class="four-filter-four">
<p class="four-filter-four--sentence">This is orange.</p>
</div>
<p class="five-filter-five">This is also unstyled (black).</p>
<div class="another-class-three">
<p>This is red.</p>
<p class="another-class-four">This is red.</p>
</div>
My best guess is to use the :not()
pseudo-class.
But I'm not entirely convinced this is the right approach, principally because I'm not sure that :not()
can handle this case.
My attempt at a solution, using :not()
:
body,
div {
display: flex;
flex-wrap: wrap;
}
p {
margin: 6px;
}
.one-filter-one p,
p[class^="one-filter-one"] {
color: blue;
}
.two-filter-two p,
p[class^="two-filter-two"] {
color: green;
}
.four-filter-four p,
p[class^="four-filter-four"] {
color: orange;
}
p:not([class*="-filter-"]) {
color: red;
}
<p class="another-class">This is red.</p>
<p>This is red.</p>
<div class="one-filter-one">
<p>This is blue.</p>
<p class="one-filter-one--paragraph">This is blue.</p>
</div>
<p class="two-filter-two">This is green.</p>
<p>This is red.</p>
<p class="another-class-two">This is red.</p>
<div class="three-filter-three">
<p>This is unstyled (black).</p>
<div><p>This is unstyled (black) too.</p></div>
</div>
<div class="four-filter-four">
<p class="four-filter-four--sentence">This is orange.</p> </div>
<p class="five-filter-five">This is also unstyled (black).</p>
<div class="another-class-three">
<p>This is red.</p>
<p class="another-class-four">This is red.</p>
</div>
Clearly this is not it, because I am not correctly selecting:
NOT descendant elements of [class*="-filter-"]
.
But I'm not clear how to do this at all.
Is there any way to do this, or am I looking to achieve the impossible in 2020, given CSS's contemporary capabilities?
Notes:
Although, in 2020, the pseudo-class :not()
has been around for the best part of a decade I've always tended to avoid using it. The only thing I do know is that the :not()
pseudo-class function can only take simple (ie. not compound) selectors.
Based on @G-Cyrillus
' brilliant suggestion (in the comments, immediately below), I have come up with the following:
body > p:not([id*="-filter-"]):not([class*="-filter-"]),
body > :not([class*="-filter-"]) > p:not([id*="-filter-"]):not([class*="-filter-"]),
body > :not([class*="-filter-"]) > :not([class*="-filter-"]) > p:not([id*="-filter-"]):not([class*="-filter-"]),
body > :not([class*="-filter-"]) > :not([class*="-filter-"]) > :not([class*="-filter-"]) > p:not([id*="-filter-"]):not([class*="-filter-"]),
body > :not([class*="-filter-"]) > :not([class*="-filter-"]) > :not([class*="-filter-"]) > :not([class*="-filter-"]) > p:not([id*="-filter-"]):not([class*="-filter-"]) {
color: red;
}
On the plus side this does work. (So, infinitely better than anything I had before).
On the minus side:
This has been a educational exercise.
The most significant thing it's taught me is that, given that :not()
cannot accept compound selectors, it's very far from straightforward to handle subsequent nested levels of markup after applying :not()
.
Given the following:
.filter-1 {
color: red;
}
:not([class^="filter-"]) p {
color: blue;
}
<div>
<div>
<p>Test.</p>
</div>
</div>
<div class="filter-1">
<div>
<p>Test.</p>
</div>
</div>
the second <p>
still shows up blue
.
Why? Because even though its grandparent has the class .filter-1
, its immediate parent does not... and that's enough to satisfy the any descendant selector (ie. the [SPACE]
) preceding the p
in the CSS Rule:
:not([class^="filter-"]) p
The only way to get around this is to replace the rule with:
:not([class^="filter-"]) > * > p
and this now works:
.filter-1 {
color: red;
}
:not([class^="filter-"]) > * > p {
color: blue;
}
<div>
<div>
<p>Test.</p>
</div>
</div>
<div class="filter-1">
<div>
<p>Test.</p>
</div>
</div>
But...
the CSS Rule is now tightly bound to the HTML structure and the amended CSS rule above won't now apply to:
<div class="filter-2">
<p>Test.</p>
</div>
See:
.filter-1 {
color: red;
}
:not([class^="filter-"]) > * > p {
color: blue;
}
<div>
<div>
<p>Test.</p>
</div>
</div>
<div class="filter-1">
<div>
<p>Test.</p>
</div>
</div>
<div class="filter-2">
<p>Test.</p>
</div>
Instead, we now need to use two rules:
:not([class^="filter-"]) > p,
:not([class^="filter-"]) > * > p
The following conclusion emerges:
We can only use
:not()
to exclude descendants when we also explicitly describe the HTML structure in the CSS.
I now understand much more clearly what @G-Cyrillus
meant by:
You need to mind the structure too
Describing an infinite number of potential descendant structures in my CSS is clearly impractical, so I've:
1) reconfigured my architecture to allow more complex descendant relationships to be described elsewhere
and
2) optimised my exclusion query to:
body > :not([id^="filter-"]):not([class^="filter-"])
Thanks very much again, @G-Cyrillus
- I've only made it as far as this due to your substantial assistance in the comment section.