CSS specificity of :not() pseudo class
Asked Answered
N

4

7

I have this small HTML:

<div id="column">
    <div class="ticker">
        <ul>
            <li>Item 1</li>
        </ul>
    </div>
</div>

For ul elements outside of the .ticker class, but inside of the #column id exists this CSS:

#column ul:not(.a):not(.b) {
    margin: 1em;
}

But inside the .ticker class I don't want this margin. So I thought I could use:

#column .ticker ul {
    margin: 0;
}

That said, I know that the specificity of the first CSS selector is higher because of the two :not() pseudo classes. But to get a higher specificity I had to append those two :not() in the second CSS snippet to the ul, too. So that works:

#column .ticker ul:not(.c):not(.d) {
    margin: 0;
}

Isn't that stupid? In fact it doesn't matter what you use in the two :not()pseudo classes. They just have to be there. This doesn't make any sense to me.

Is that simply a part of CSS3 which is not perfect or is there a solution which my brain didn't come up with yet?

See it in action here: http://jsfiddle.net/9BDw5/2/

Newfangled answered 22/5, 2014 at 18:31 Comment(2)
You have calculated the CSS specificity correctly so your CSS (though a bit awkward perhaps) is good. There might be a better way of designing the overall CSS scheme, but it depends in part on the HTML structure of the web page in question.Bushwa
Yes - psuedoclasses and psuedoelements have the same specificity as their real counterparts. This is not a CSS issue so much as it's a CSS and markup design/practices issue.Sldney
E
7

It's not just you; this is indeed one of the fundamental pitfalls of specificity as a CSS concept.

A simpler solution that is equally valid would be to repeat your .ticker class selector so you have this:

#column .ticker.ticker ul {
    margin: 0;
}

This way you do not have to modify your element to add an extra class just for the sake of increasing selector specificity.

The spec verifies this:

Note: Repeated occurrances of the same simple selector are allowed and do increase specificity.

On a side note, remember that the specificity of the :not() pseudo-class is strictly defined (in the same section of the spec) as equal to that of its argument. So :not(#id) and #id have the same specificity, and likewise for :not(E):not(.a) and E.a. The :not portion does not count at all, not even as a pseudo-class.

This limitation in specificity will be addressed in Selectors 4, which enhances :not() to accept a comma-delimited list of selectors. The specificity of a :not() that contains a selector list will be that of the most specific selectors in the list, so the specificity of ul:not(.c, .d) is equal to 1 type selector and 1 class selector, compared to ul:not(.c):not(.d) which is equal to 1 type selector and 2 class selectors. This makes it tremendously useful in excluding any number of classes from a match.

Ellga answered 23/6, 2014 at 11:19 Comment(1)
Thank you for pointing out the note in the CSS spec, good explanation and insight.Bushwa
B
0

Here is an alternative, and somewhat cleaner approach.

Add a fictitious class name to your div.ticker element:

<div id="column">
    <ul>
        <li>Outside Item 1</li>
    </ul>
    <div class="ticker extra-tag">
        <ul>
            <li>Item 1</li>
        </ul>
    </div>
</div>

and modify the CSS as follows:

#column ul:not(.a):not(.b) {
    margin-left: 1em;
}

#column .ticker.extra-tag ul {
    margin-left: 0;
}

So, the specificity of the first rule is 1-1-2 and for the second rule 1-1-2.

The two :not() count as two classes, so you need to have at least two classes in the second rule, which I implemented by using the fictitious class name .extra-tag.

The fictitious class is probably more computationally efficient than adding the extra :not() pseudo-class.

See demo at: http://jsfiddle.net/audetwebdesign/7beNx/

Learn more about CSS specificity at: http://www.w3.org/TR/css3-selectors/#specificity

Bushwa answered 22/5, 2014 at 19:10 Comment(2)
You don't even have to add the extra class - you can simply repeat .ticker so you have #column .ticker.ticker ul, which is perfectly acceptable and does give the same specificity bonus. (Shall I post it as a separate answer since it's quite different from yours?)Ellga
@Ellga Interesting variation, please post with comments, this trick would be very useful for cases where you may not be able to add the extra class name.Bushwa
Z
0

As mentioned above chaining the :not() pseudo-class increases specificity by one each time.

This article explains it very nicely

Zeitler answered 22/1, 2016 at 11:4 Comment(0)
R
0

CSS has come a long way since this question was raised. But since I still happened to stumble here, I thought to present two solutions employing modern selectors.

The :not() pseudo class now supports a selectors list, the specificity is not calculated as the sum of the list, but instead it counts the same as the highest of the elements in the list. In this case this would lead to a specificity of 1-1-1 for both rules:

#column ul:not(.a,.b) {
    margin-left: 1em;
}
#column .ticker ul {
    margin: 0;
}

CSS Selectors Level 4 also introduced the :where() pseudo class which reduces the specificity of selectors inside to zero, leading to a specificity of 1-0-1 for the first rule and 1-1-1 for the second:

#column ul:where(:not(.a):not(.b)) {
    margin-left: 1em;
}
#column .ticker ul {
    margin: 0;
}
Repulse answered 23/7 at 8:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.