Can type selectors be repeated to increase specificity?
Asked Answered
D

1

9

The spec states regarding calculating CSS specificity: (bold mine)

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

So for example .class.class {} has twice the specificity than .class {} - DEMO

However, regarding the term 'simple selector' the spec has this to say: (bold mine)

A simple selector is either a type selector or universal selector followed immediately by zero or more attribute selectors, ID selectors, or pseudo-classes, in any order.

So since the spec says that repeated occurrences of the same simple selector are allowed - this would imply that you could repeat type selectors as well.

Well quite obviously something like this won't work: h1h1 { },

so I tried this: h1[]h1[] {} - which doesn't work either,

so I'm wondering if there is a way to do this?

Dogger answered 3/2, 2015 at 13:12 Comment(2)
I have a feeling this is actually a fault in the way the spec has been written up..Afterword
I would have to agree, I don't think I have ever seen anything like that before. I may have to have a mess around with it and test it out but it doesn't seem likely that this would be correct.Less
C
15

It is possible to increase the specificity of a selector using type selectors, but not conventionally. The reason for this is explained below, but for those who are simply looking for an alternative, there are two of these. You can either chain :not() pseudo-classes containing type selectors in a single compound selector:

h1                      {} /* 1 type  -> specificity = 0-0-1 */
h1:not(_)               {} /* 2 types -> specificity = 0-0-2 */
h1:not(_):not(_)        {} /* 3 types -> specificity = 0-0-3 */
h1:not(_):not(_):not(_) {} /* 4 types -> specificity = 0-0-4 */

Or, if you need to support legacy browsers that don't support :not(), you can add redundant type selectors such as html and body to the beginning of a complex selector, although you are far more limited in this case as you may not be able to account for all elements:

h1                {} /* 1 type  -> specificity = 0-0-1 */
body h1           {} /* 2 types -> specificity = 0-0-2 */
html body h1      {} /* 3 types -> specificity = 0-0-3 */
html body tr > td {} /* 4 types -> specificity = 0-0-4, assumes every td is a child of tr */

Needless to say, these are considered specificity hacks; as with all other CSS hacks, use them sparingly, if at all.


A compound selector may only have at most exactly one type selector preceding all other simple selectors. From Selectors 3 (which calls this a sequence of simple selectors):

A sequence of simple selectors is a chain of simple selectors that are not separated by a combinator. It always begins with a type selector or a universal selector. No other type selector or universal selector is allowed in the sequence.

And Selectors 4:

A compound selector is a sequence of simple selectors that are not separated by a combinator. If it contains a type selector or universal selector, that selector comes first in the sequence. Only one type selector or universal selector is allowed in the sequence.

Only type and universal selectors are subject to this rule; you may combine and repeat other simple selectors to increase specificity. Perhaps the spec could have reminded the reader about this in the section on calculating specificity, but I don't think it's absolutely necessary.

The reason for this rule is never stated explicitly, but it is fairly easy to deduce:

  • Remember that a type selector consists of simply an identifier, e.g. h1. This is unlike other simple selectors which have their own distinguishing symbols in the grammar, such as an ID (#), a class (.), a pseudo-class (:), or an attribute selector ([]). You would not be able to have multiple consecutive type selectors without a way to parse them separately.

  • And even if you could chain type selectors, for example if you had another simple selector between them, the only possible use for this would be as a specificity hack, as described in the question, which means you would only be able to use it if all the type selectors were the same; no other combination of type selectors could work.

    This is because Selectors assumes that the document language defines every element to have exactly one element type. For example, in HTML, an h1 is always an h1; it can never be any other type of element. A compound selector asking for an element that is both an h1 as well as a p can never match anything, for the same reason something like [type=text][type=password] can never match anything in a document language that does not support duplicate attributes.

However, with the above points in mind, it is still possible to create a compound selector that contains more than one type selector for specificity — by using the :not() pseudo-class:

  • The specificity of a :not() pseudo-class is equal to its argument. The pseudo-class itself is not counted. This is mentioned in the first link. This means the specificity of :not(h1) is equivalent to h1 — one type selector.

  • Since an element can only be of exactly one type, this means :not() with any other type selector will be a guaranteed match.

  • Since a compound selector may include any number of pseudo-classes, you can repeat the negation as many times as necessary, even if the negations all use the same type selector.

  • And since Selectors doesn't care if a selector makes sense in the context of any particular document language, you can use a type selector that is guaranteed to never match any element in a conforming HTML document as long as it satisfies the Selectors grammar for a type selector. A type selector consists of nothing but a CSS identifier, so any CSS identifier is fair game. Including _.

Careless answered 3/2, 2015 at 13:45 Comment(4)
Thanks for this very detailed answer! Just curious though: why would you consider the selector body h1 {} a hack? Also I thought of another possible way of adding specificity: something like h1:nth-child(n) {} - but that actually looks far more hackyDogger
@Danield: Because there's no real reason you should have the body selector there, since you can generally assume that h1 will always be in body. I feel a little dirty any time I am adding selectors for the sole purpose of adding specificity :)Careless
@BoltClock, do you happen to know of any browser limitations for h1:not(i)? Didn't find any, but can't use caniuse for it, either. :)Plasm
@Andrei Gheorghiu: No known limitations. I'm sure you don't have to worry about that not working in any browser made in the last 10 years (except IE8, where :not() doesn't work anyway) :)Careless

© 2022 - 2024 — McMap. All rights reserved.