Arthur Rey
Arthur Rey

Reputation: 3058

Selector for element having one of two classes, but not both

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

Answers (3)

NoOneSpecial
NoOneSpecial

Reputation: 755

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

Upvotes: 2

BoltClock
BoltClock

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

Rounin
Rounin

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

Related Questions