Search code examples
csscss-selectorspseudo-classcss-specificity

Calculating CSS selector specificity for :not() pseudo-class


I already know how to calculate selector specificity for CSS (the a/b/c/d mentioned in specs). However, I am having trouble figuring how to calculate it for the :not() pseudo-class. See the following example:

input:not([type="text"],[type="password"],.someClass) {...}
input:not(#someId[type="text"]) {...}

Assuming both of them apply to one element, should these two be calculated as (0011) and therefore they should be ordered according to order of appearance? Or should the selectors inside the :not() pseudo-class be further calculated separately, depending on which one matches, as a second step to determine the one that has precedence over the other?


Solution

  • Assuming both of them apply to one element, should these two be calculated as (0011) and therefore they should be ordered according to order of appearance? Or should the selectors inside the :not() pseudo-class be further calculated separately depending on which one match as a second step to determine the one that have precedence over the other?

    If you're implementing Selectors 3, they should not be counted at all. As already mentioned, both of your selectors are invalid according to that spec because it only defines :not() to accept a single simple selector at a time.

    If you expand them out so that they validate (following the instructions/examples given here), then their specificities will be calculated as follows:

    /* 2 attributes, 1 class, 1 type -> specificity = 0-3-1 */
    input:not([type="text"]):not([type="password"]):not(.someClass)
    
    /* 
     * 1 ID, 1 type        -> specificity = 1-0-1
     * 1 attribute, 1 type -> specificity = 0-1-1
     */
    input:not(#someId), input:not([type="text"])
    

    Because Selectors 3 says:

    Selectors inside the negation pseudo-class are counted like any other, but the negation itself does not count as a pseudo-class.

    Also, in response to your comment:

    True, according to specs, simple selectors only. But some browsers support multiple ones. Some of them don't, and some dropped them later. Also, you can write the same rule even with simple selectors like this instead: input:not([type="text"]):not([type="password"]):not(.someClass) which is better and work as well. Does this mean it should be calculated as 0031, then? How about those that support multiple ones, how do they calculate?

    The only browser I know of that has ever supported multiple selectors within :not() is Firefox 3.0, and it does so because of a bug. Selectors 3 never allowed :not() to contain multiple selectors — that is only introduced in Selectors 4, for which specificity calculation hasn't even been clearly defined yet1, so even if you were trying to implement Selectors 4 (which I seriously doubt you are), you'll be stuck.

    It's not clear to me how Firefox 3.0 implemented specificity with its version of the :not() selector and I don't have a copy of it to test with, but I think it's safe to assume that it doesn't matter anymore as it was never the intended behavior anyway. OK so I picked up Firefox 3.0 beta 1, 3.0.0, 3.0.18 and 3.1 beta 1, and none of them reproduce this behavior at all. So there you have it.


    1 Note that both the current 2013 ED and the 2011 FPWD are consistent in saying that the specificity of :not() is equal to that of its most specific argument, but this may change in the future.