Query
I am attempting to devise a CSS selector that targets all children within a specified parent, excluding them if any element along the path has a specific class.
Background
In my JavaScript project, I am developing a materialization class that transforms certain elements into their material counterparts. This process operates on a top-level application where users can create their own apps. I aim to specify that a particular set of elements should be exempt from this transformation.
Illustration
The following should be included:
<div>
<input />
</div>
While this should not be selected:
<div class="no-material">
<input />
</div>
The complexity arises because this exclusion label can appear anywhere. For example:
<main>
<section class="no-material">
<form>
<fieldset>
<input />
</fieldset>
</form>
</section>
</main>
Or it might look like this:
<main>
<section>
<form class="no-material">
<fieldset>
<input />
</fieldset>
</form>
</section>
</main>
Past Attempts
I have made several efforts to address this issue. The most effective approach so far is:
div:not(.no-material) > input:not(.no-material), div:not(.no-material) *:not(.no-material) input:not(.no-material)
However, this method still yields some false positives. One alternative involves increasing the specificity by adding numerous levels, such as:
div:not(.no-material) > input:not(.no-material),
div:not(.no-material) > *:not(.no-material) > input:not(.no-material),
div:not(.no-material) > *:not(.no-material) > *:not(.no-material) > input:not(.no-material)
This solution would require 20-50 levels (or more?), which is not very efficient.
Interactive Version
You can test your selectors by modifying the cssSelector in the provided JavaScript code snippet.
let cssSelector = [
// Independent selectors
'div:not(.no-material) > input:not(.no-material)',
'div:not(.no-material) *:not(.no-material) input:not(.no-material)'
].join(',');
// This will get elements and run their names. We should get yes1-5, but not no1-5.
let inputs = document.querySelectorAll(cssSelector);
for (let input of inputs) console.log(input.getAttribute('name'));
<!-- Do not edit HTML, just the CSS selector -->
<main style="display: none;">
<!-- Not selectable -->
<div class="no-material">
<input name="no-1">
</div>
<div>
<input name="no-2" class="no-material">
</div>
<div>
<label class="no-material">
<input name="no-3">
</label>
</div>
<div>
<label class="no-material">
<span>
<input name="no-4">
</span>
</label>
</div>
<div>
<label>
<span class="no-material">
<input name="no-5">
</span>
</label>
</div>
<!-- Selectable -->
<div>
<input name="yes-1">
</div>
<div>
<input name="yes-2">
</div>
<div>
<label>
<input name="yes-3">
</label>
</div>
<div>
<label>
<span>
<input name="yes-4">
</span>
</label>
</div>
<div>
<label>
<span>
<input name="yes-5">
</span>
</label>
</div>
</main>
<!-- Do not edit HTML, just the CSS selector -->
Note: While other approaches exist, such as iterating through all children of an element with the '.no-material' class and assigning the 'no-material' class to each, this is resource-intensive. My preference is to resolve this issue using a CSS selector if feasible.
Thank you