3

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.

5
  • :not(.class1):not(.class2) works perfectly
    – Abin Thaha
    Commented May 11, 2018 at 10:05
  • :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?
    – Arthur Rey
    Commented May 11, 2018 at 10:07
  • I don't think css :not() works here jsfiddle.net/d9bk9m04 Commented May 11, 2018 at 10:07
  • I don't know, I didn't downvote, but now I'm a little confused. That li.active.last isn't missing both classes - since it does have the "last" class. As stated, :not(.first):not(.last) will match elements that don't have either class. But that's simply not what you're looking for.
    – BoltClock
    Commented May 11, 2018 at 10:07
  • @BoltClock I want the <li> to be red whenever it has the class active and not both first and last.
    – Arthur Rey
    Commented May 11, 2018 at 10:10

3 Answers 3

4

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>

0
2

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>

1

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

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

  1. 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)

  2. 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.

  3. 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))

  4. 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).

  5. 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)))

1
  • 1
    Great breakdown. Selectors 4 now also introduces :is(), effectively an OR operator that doesn't require duplicating the rest of the selector. As "A XOR B" can also be expressed as "(A OR B) AND (NOT (A AND B))" the selector can therefore be simplified to li.active:is(.first, .last):not(.first.last), removing the need for any NANDs. I don't know of any Selectors 3-compatible solution that's simpler than the one I answered with, though level 4 :not() and :is() are reasonably well-supported at this point.
    – BoltClock
    Commented Nov 15, 2021 at 2:01

Not the answer you're looking for? Browse other questions tagged or ask your own question.