Question
I have this layout:
<div id="sectors">
<h1>Sectors</h1>
<div id="s7-1103" class="alpha"></div>
<div id="s8-1104" class="alpha"></div>
<div id="s1-7605" class="beta"></div>
<div id="s0-7479"></div>
<div id="s2-6528" class="gamma"></div>
<div id="s0-4444"></div>
</div>
With these CSS rules:
#sectors {
width: 584px;
background-color: #ffd;
margin: 1.5em;
border: 4px dashed #000;
padding: 16px;
overflow: auto;
}
#sectors > h1 {
font-size: 2em;
font-weight: bold;
text-align: center;
}
#sectors > div {
float: left;
position: relative;
width: 180px;
height: 240px;
margin: 16px 0 0 16px;
border-style: solid;
border-width: 2px;
}
#sectors > div::after {
display: block;
position: absolute;
width: 100%;
bottom: 0;
font-weight: bold;
text-align: center;
text-transform: capitalize;
background-color: rgba(255, 255, 255, 0.8);
border-top: 2px solid;
content: attr(id) ' - ' attr(class);
}
#sectors > div:nth-of-type(3n+1) {
margin-left: 0;
}
#sectors > div.alpha { color: #b00; background-color: #ffe0d9; }
#sectors > div.beta { color: #05b; background-color: #c0edff; }
#sectors > div.gamma { color: #362; background-color: #d4f6c3; }
I use jQuery to add the unassigned
class to sectors that don't otherwise
have one of the classes alpha
, beta
or gamma
:
$('#sectors > div:not(.alpha, .beta, .gamma)').addClass('unassigned');
Then I apply some different rules to that class:
#sectors > div.unassigned {
color: #808080;
background-color: #e9e9e9;
opacity: 0.5;
}
#sectors > div.unassigned::after {
content: attr(id) ' - Unassigned';
}
#sectors > div.unassigned:hover {
opacity: 1.0;
}
And everything works flawlessly in modern browsers.
But seeing as the [:not()
selector in jQuery](http://api.jquery.com/not-
selector) is based on :not()
in
CSS3, I was thinking I could
move it directly into my stylesheet so I wouldn't have to rely on adding an
extra class using jQuery. Besides, I'm not really interested in supporting
older versions of IE, and other browsers have excellent support for the
:not()
selector.
So I try changing the .unassigned
portion above to this (knowing I will only
have sectors Α, Β and Γ in my layout):
#sectors > div:not(.alpha, .beta, .gamma) {
color: #808080;
background-color: #e9e9e9;
opacity: 0.5;
}
#sectors > div:not(.alpha, .beta, .gamma)::after {
content: attr(id) ' - Unassigned';
}
#sectors > div:not(.alpha, .beta, .gamma):hover {
opacity: 1.0;
}
But as soon as I do this it stops working — in all browsers! My unassigned sectors aren't grayed out, faded out or labeled 'Unassigned' anymore.
Updated but not so interactive jsFiddle preview
Why does the :not()
selector work in jQuery but fail in CSS? Shouldn't it
work identically in both places since jQuery claims to be "CSS3 Compliant", or
is there something I'm missing?
Is there a pure CSS workaround for this or will I have to rely on a script?
Answer
Why does the
:not()
selector work in jQuery but fail in CSS? Shouldn't it work identically in both places since jQuery claims to be "CSS3 Compliant", or is there something I'm missing?
Perhaps it should , but it turns out that it doesn't : jQuery extends the
:not()
selector such that you can pass any selector to
it, no matter how complex it may be, and
I suspect that the main reason for this is for parity with the .not()
method, which also takes any arbitrarily complex
selector and filters accordingly. It does in a way maintain a CSS-like syntax,
but it extends from what's defined in the standard.
As another example, this works just fine (I know it's an incredibly ludicrous example compared to what's given in the question, but it's just for illustrative purposes):
/*
* Select any section
* that's neither a child of body with a class
* nor a child of body having a descendant with a class.
*/
$('section:not(body > [class], body > :has([class]))')
Remember that passing a comma-separated list of selectors to :not()
means
filtering elements that don't match any of the listed selectors.
Now the :not()
pseudo-class in Selectors level
3, on the other hand, is very
limited by itself. You can only pass a single simple selector as an argument
to :not()
. This means you can pass only any one of these at a time:
- Universal selector (
*
), optionally with a namespace - Type selector (
a
,div
,span
,ul
,li
, etc), optionally with a namespace - Attribute selector (
[att]
,[att=val]
, etc), optionally with a namespace - Class selector (
.class
) - ID selector (
#id
) - Pseudo-class (
:pseudo-class
)
So, here are the differences between jQuery's :not()
selector and the current standard's
:not()
selector:
-
First and foremost, to answer the question directly: you can't pass a comma-separated selector list. 1 For example, while the given selector works in jQuery as demonstrated in the fiddle, it isn't valid CSS:
/* If it's not in the Α, Β or Γ sectors, it's unassigned */ #sectors > div:not(.alpha, .beta, .gamma)
Is there a pure CSS workaround for this or will I have to rely on a script?
Thankfully, in this case, there is. You simply have to chain multiple :not()
selectors, one after another, in order to make it valid CSS:
#sectors > div:not(.alpha):not(.beta):not(.gamma)
It doesn't make the selector that much longer, but the inconsistency and inconvenience remain evident.
Updated interactive jsFiddle preview
-
You can't combine simple selectors into compound selectors for use with
:not()
. This works in jQuery, but is invalid CSS:/* Do not find divs that have all three classes together */ #foo > div:not(.foo.bar.baz)
You'll need to split it up into multiple negations (not just chain them!) to make it valid CSS:
#foo > div:not(.foo), #foo > div:not(.bar), #foo > div:not(.baz)
As you can see, this is even more inconvenient than point 1.
-
You can't use combinators. This works in jQuery, but not CSS:
/*
- Grab everything that is neither #foo itself nor within #foo.
- Notice the descendant combinator (the space) between #foo and *. */ :not(#foo, #foo *)
This is a particularly nasty case, primarily because it has no proper workaround. There are some loose workarounds (1 and 2), but they almost always depend on the HTML structure and are therefore very limited in utility.
- In a browser that implements
querySelectorAll()
and the:not()
selector, using:not()
in a selector string in a way that makes it a valid CSS selector will cause the method to return results directly, instead of falling back to Sizzle (jQuery's selector engine which implements the:not()
extension). If you're a stickler for performance, this is a positively minuscule bonus you'll definitely salivate over.
The good news is that Selectors 4 enhances the :not()
selector to allow a comma-
separated list of complex selectors. A complex selector is simply either a
lone simple or compound selector, or an entire chain of compound selectors
separated by combinators. In short, everything you see above.
This means that the jQuery examples above will become valid level 4 selectors, which will make the pseudo-class much, much more useful when CSS implementations begin supporting it in the coming years.
1 Although [this article](http://kilianvalkhof.com/2008/css-xhtml/the-
css3-not-selector) says that you can pass a comma-separated list of selectors
to :not()
in Firefox 3, you're not supposed to be able to. If it works in
Firefox 3 as that article claims, then it's because a bug in Firefox 3 for
which I can't find the ticket anymore, but it shouldn't work until future
browsers implement future standards. Seeing how often that article is cited to
date, I've left a [comment](http://kilianvalkhof.com/2008/css-xhtml/the-
css3-not-selector/#comment-164654) to this effect, but seeing also how old the
article is and how infrequently the site is being updated, I'm really not
counting on the author coming back to fix it.