How to use a CSS selector to select html elements with either one of two classes, but not both of the classes
Or how to do XOR in a CSS selector
What you need is XOR - Exclusive or
Exclusive or is a logical operation that is true if and only if its arguments differ (one is true, the other is false).
XOR
Input A |
Input B |
Outcome Q |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
0 |
Unfortunately there's not an XOR operator in CSS which makes this a bit tricky.
But we have both AND ant NOT, and those two together gets us NAND (NOT AND).
The cool thing with NAND is that it is possible to combine NAND to express any Boolean expression!
So we need XOR which can be realized with NAND like this
![XOR realized by combining NAND](https://cdn.statically.io/img/upload.wikimedia.org/wikipedia/commons/thumb/f/fa/XOR_from_NAND.svg/450px-XOR_from_NAND.svg.png)
XOR = ( A NAND ( A NAND B ) ) NAND ( B NAND ( A NAND B ) )
So lets say A = the CSS class first, and B = the CSS class last, then we get this:
li.active:not(:not(.first:not(.first.last)):not(.last:not(.first.last)))
{
background-color: #F00;
}
<ul class="tabs">
<li class="first">1st</li>
<li class="active last">2nd</li>
</ul>
<ul class="tabs">
<li class="active first last">1st</li>
</ul>
Lets break it down
In CSS AND is realized by chaining two selectors together, so in order to get CSS version of first NAND last
we do :not(.first.last)
Next we need ( first NAND ( first NAND last ) )
which we get with :not(.first:not(.first.last))
, again chaining together first
with the expression :not(.first.last)
from point 1 above. This gives us the left part of the complete expression.
The second and right part of the expression is almost identical to the left part
Part |
Expression |
Left |
( A NAND ( A NAND B ) ) |
Right |
( B NAND ( A NAND B ) ) |
So all we need to to is to swap out the first A for B, or in our case, swap out the first first
for last
, and that should give us the right and last half of the expression, :not(.last:not(.first.last))
All we now need to do it to AND those two parts together and then negate them with a NOT.
So again we do AND by chaining the two parts together which result in :not(.first:not(.first.last)):not(.last:not(.first.last))
.
And then we add the final NOT to get not(:not(.first:not(.first.last)):not(.last:not(.first.last)))
.
And that's the complet XOR expression realized with NAND (NOT and AND).
The final step is to limit the entire XOR-expression to only li-tags with the class active.
So we chain it together with a basic tag+class selector and we get the final result: li.active:not(:not(.first:not(.first.last)):not(.last:not(.first.last)))
:not(.class1):not(.class2)
doesn't work. It selects element missing at least one class, not both. Meaning in the first example the 2nd tab is not red. Why the downvotes?:not()
works here jsfiddle.net/d9bk9m04