Reputation: 3058
How can I select elements having one of two classes, but not both?
I tried :not(.class1, .class2)
and :not(.class1):not(.class2)
but they seem to select elements missing either class. I need to select elements that don't have both classes.
I also tried :not([class='class1 class2'])
as this accepted answer states, but it doesn't seem to select anything.
Here is a sample of what I want -
ul.tabs > li.active:not([class='first last']) {
background-color: #F00;
}
<ul class="tabs">
<li class="first">1st</li>
<li class="active last">2nd</li> <!-- Should be red -->
</ul>
<ul class="tabs">
<li class="active first last">1st</li> <!-- Should NOT be red -->
</ul>
The <li>
's have other classes I'm not responsible over. I'm wondering if there is a way of ignoring other classes for the last selector I tried.
Upvotes: 2
Views: 2539
Reputation: 755
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).
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 = ( 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)))
Upvotes: 2
Reputation: 723388
You want :not(.first), :not(.last)
which is the Selectors level 3-supported version of :not(.first.last)
(from Selectors 4, not yet supported by all browsers, as well as jQuery):
ul.tabs > li.active:not(.first), ul.tabs > li.active:not(.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>
As Rounin points out, though, you should be able to just use :first-of-type
and :last-of-type
, or :first-child
and :last-child
, instead of bloating the markup with with "first" and "last" classes (if those classes represent the same things as the pseudo-classes).
In fact, if you do switch to those pseudo-classes, you can simplify your CSS to just one selector by condensing :not(:first-of-type), :not(:last-of-type)
to :not(:only-of-type)
:
ul.tabs > li.active:not(:only-of-type) {
background-color: #F00;
}
<ul class="tabs">
<li>1st</li>
<li class="active">2nd</li>
</ul>
<ul class="tabs">
<li class="active">1st</li>
</ul>
Upvotes: 4
Reputation: 29453
In this situation, you can use two CSS pseudo-classes:
:first-of-type
:last-of-type
and take advantage of the CSS cascade, by declaring the rule for :first-of-type
beneath the rule for :last-of-type
.
Working Example:
.tabs li:last-of-type.active {background-color: rgb(255, 0, 0);}
.tabs li:first-of-type.active {background-color: transparent;}
<ul class="tabs">
<li>1st</li>
<li class="active">2nd</li> <!-- Should be red -->
</ul>
<ul class="tabs">
<li class="active">1st</li> <!-- Should NOT be red -->
</ul>
Upvotes: 2