Controlling the Specificity

In the pre­vi­ous ar­ti­cle about con­di­tions for CSS vari­ables I talked about things that could be used rather soon, even if the sup­port for them is not there yet. In this ar­ti­cle I'll go even fur­ther —I'll be talk­ing about one thing from the CSS Se­lec­tors Level 4, which is even less adopted.

The part of this spec I'll be look­ing at to­day is the new, en­hanced :not(). Im­por­tant dis­claimer: the fea­ture I would talk about have al­most to no sup­port at the mo­ment (only lat­est Sa­fari?), and even if it did, I wouldn't rec­om­mend to use it as some­thing other than ex­per­i­ment. You'll see why. And af­ter dis­cussing what be­comes pos­si­ble with this new :not() I'll de­scribe one more us­able sim­i­lar thing and then pro­pose a few things that I think should be there in CSS in­stead.

The All-New Negation Pseudoclass

In Se­lec­tors Level 3 :not() could have only a sin­gle sim­ple se­lec­tor in­side of it. The lev­eled-up ver­sion al­lows for so much more!

The spec­i­fi­ca­tion for the :not() doesn't say much about its new fea­tures. Al­most all it says is that in­stead of a sim­ple se­lec­tor you can put a se­lec­tor list in­side. But that alone means a lot.

Selector Lists

So, now we can use comma-sep­a­rated se­lec­tor lists in­side any :not(). The com­mas in usual comma-sep­a­rated se­lec­tor lists are an equiv­a­lent of log­i­cal or, but when used in­side the :not() they ac­tu­ally be­come and.

Look at this ex­am­ple —such se­lec­tor would tar­get all but­tons that are not hov­ered and not fo­cused at the same time:

button:not(:hover, :focus)

And it is ba­si­cally an equiv­a­lent of

button:not(:hover):not(:focus)

The dif­fer­ence there is how the speci­ficity works for se­lec­tor lists in­side :not(), here is what Spec says:

The speci­ficity of a :not() pseudo-class is re­placed by the speci­ficity of the most spe­cific com­plex se­lec­tor in its se­lec­tor list ar­gument.

That means that the speci­ficity of :not(:hover, :fo­cus) is the same as the speci­ficity of a sin­gle class, so it is re­ally dif­fer­ent than if you'd just use mul­ti­ple :not()s. You can fit any num­ber of se­lec­tors in­side a se­lec­tor list in­side :not(), and you still would get the speci­ficity of only the high­est one. And if the speci­ficity would be the same for each se­lec­tor in a list, you'll get only this speci­ficity.

This is a first cru­cial fea­ture for our case.

Complex Selectors

The sec­ond cru­cial fea­ture is that those se­lec­tor lists can now con­tain com­plex se­lec­tors. Be­fore, you could put only a sim­ple se­lec­tor in­side :not(), but now there won't be such limit, so you could do stuff like :not(a.foo:nth-child(2n+1):hover) and it would just work.

An­other in­ter­est­ing thing in :not() now ac­cept­ing com­plex se­lec­tors, is that it can also ac­cept se­lec­tors with com­bi­na­tors like :not(.foo + .bar).

Negation of Negation

Of course, since the :not() se­lec­tor is it­self a com­plex one, you can now use it in­side other :not()s.

And, yeah, we're at the point where the magic would hap­pen. What does dou­ble nega­tion mean in logic? It re­turns the value to its orig­i­nal bi­nary state.

If we'd have some­thing like that:

:not(:not(:hover))

That would work just the same as a :hover pseudo­class. That's rather sim­ple. But what would hap­pen if we'd have a se­lec­tor list there?

:not(:not(:hover), :not(:focus))

As the se­lec­tor lists in­side :not() work as a log­i­cal and, and given that each of the se­lec­tors would be re­turned to its orig­i­nal mean­ing, the re­sult would be al­most the same as :hover:fo­cus. The dif­fer­ence would be that the speci­ficity of the dou­ble negated se­lec­tor wouldn't be the same as of the usual com­plex one. Each of the nested :not() would have a speci­ficity of a sin­gle pseudo­class, and due to how se­lec­tor lists work in­side :not(), the speci­ficity of the whole con­struc­tion would be equal, again, to a speci­ficity of a sin­gle pseudo­class.

Reducing the Specificity

I think you can al­ready guess what all of this means. That's right —us­ing the new :not() we can now write se­lec­tors with a speci­ficity of a sin­gle class. And, ac­tu­ally, we can write al­most any se­lec­tor this way.

So, if we'd like to have a mul­ti­class se­lec­tor with some states, like .foo.bar.baz:link:hover and for it to have a speci­ficity of a sin­gle class, so it could be eas­ier over­rid­den later on, we could rewrite this se­lec­tor this way:

:not(:not(.foo), :not(.bar), :not(.baz), :not(:link), :not(:hover))

What About Combinators?

But what if we'd like to have more com­plex se­lec­tors with com­bi­na­tors, like this one?

.foo:hover > .bar .baz

They're pos­si­ble too, but with a slightly more com­plex code. What would help us is a uni­ver­sal se­lec­tor. Here is how the se­lec­tor above could look like if we'd want it to have a speci­ficity of a sin­gle class:

:not(:not(:not(:not(:not(:not(.foo), :not(:hover)) > *), :not(.bar)) *), :not(.baz))

That looks aw­ful, right? But that works! (Here is a test at Code­Pen with this se­lec­tor, if you'd open it in the lat­est Sa­fari, you'll see it in ac­tion.)

Of course, it would be­come a bit read­able if we'd use some in­den­ta­tions and stuff:

:not(
    :not(
        :not(
            :not(
                :not(
                    :not(.foo),
                    :not(:hover)
                ) > *
            ),
            :not(.bar)
        ) *
    ),
    :not(.baz)
)

Still ugly, but man­age­able (and now, if you'd imag­ine all of the :not() and uni­ver­sal se­lec­tors would dis­ap­pear, you could read it al­most as our orig­i­nal se­lec­tor).

But why it works? Due to how the se­lec­tor match­ing mech­a­nism works, the se­lec­tors with com­bi­na­tors would match from right to left, so the right­most se­lec­tor would be al­ways the one that matches the el­e­ment we're test­ing the :not() on, so we could safely use just a uni­ver­sal se­lec­tor in­stead of it if we need to just check the par­ents. And the uni­ver­sal se­lec­tor brings no speci­ficity with it.

The al­go­rithm be­yond rewrit­ing the se­lec­tor this way is rather sim­ple: we go from right to left, re­plac­ing re­cur­sively all the parts with the dou­ble nega­tions, so for when we have nested com­bi­na­tors, we would have more nested :not()s. And this way we would al­ways be guar­an­teed to have the same speci­ficity on each step.

Complete Control

It worth men­tion­ing that we can al­ready in­crease the speci­ficity of any given class just by mul­ti­ply­ing it (You can read about this method in Harry Roberts' ar­ti­cle on speci­ficity hacks (and, as Harry, I first saw this method in Math­ias By­nens' talk).), so .foo.foo.foo matches just the same as .foo, but with a speci­ficity of three classes. And as we can now both re­duce and in­crease the speci­ficity of al­most any given se­lec­tors, that means we can, fi­nally, con­trol the speci­ficity of our se­lec­tors, re­gard­less of their com­plex­ity. Of course, with the ex­cep­tion that we can't re­duce the speci­ficity to be less than the one of the biggest sin­gle se­lec­tor's one, so we can't make a se­lec­tor that con­tains a class to be as spe­cific as an el­e­ment se­lec­tor or a uni­ver­sal one.

Preprocessors?

Yes, it is pos­si­ble to pro­gram­mat­i­cally con­vert al­most any se­lec­tor to the same se­lec­tor with any cus­tom speci­ficity from one class to any given num­ber. But I in­ten­tion­ally won't im­ple­ment it now. As I men­tioned at the start —the browser sup­port for the new :not() is not yet there, and even if it would be there, the gen­er­ated code we'd get for such se­lec­tors would be aw­ful. If you'd like a small chal­lenge, you can go and im­ple­ment it just for fun us­ing any pre­proces­sor or post­proces­sor you like, but I don't rec­om­mend on us­ing it any­where close to pro­duction.

Possible Usage

One of the eas­i­est tar­gets for con­trol­ling the speci­ficity are any re­sets or nor­mal­iz­ing styles —right now they of­ten con­tain things like at­tribute se­lec­tors and pseudo­classes like :nth-child, and given that those se­lec­tors would also have the el­e­ment se­lec­tors, they would al­ways be higher than a sin­gle class in speci­ficity, which would make it harder to over­ride it in the code for your blocks.

For ex­am­ple, you can look at one part of Nico­las Gal­lagher's nor­mal­ize.css:

/**
 * 1. Remove the bottom border in Firefox 39-.
 * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
 */
abbr[title] {
  border-bottom: none; /* 1 */
  text-decoration: underline; /* 2 */
  text-decoration: underline dotted; /* 2 */
}

Here if you'd like to have a com­po­nent that uses <abbr> and you'd want a bor­der or text-dec­o­ra­tion other than un­der­lined, you couldn't use a sin­gle class for this com­po­nent in your CSS along­side us­ing nor­mal­ize.css —you'd need to over­ride the speci­ficity of two classes in­stead.

But if we could re­duce the speci­ficity of each se­lec­tor in our re­sets and nor­mal­izes to the small­est —of a sin­gle el­e­ment or a sin­gle class —those tools would be­come even more pow­er­ful and flex­ible.

An­other area where the con­trol over speci­ficity is a must have are any com­plex CSS method­olo­gies. The eas­i­est ex­am­ple would be Harry Roberts' itCSS which (There is not much writ­ten on it any­where, if you'd like to read more on it, you can try this ar­ti­cle by Lu­bos Kmetko.) have lay­ers of se­lec­tors united by sim­i­lar area of re­spon­si­bil­ity. If we could split those lay­ers so they wouldn't merge in their speci­ficity, we would ob­tain the ul­ti­mate power over CSS (ok, I ex­ag­ger­ated it a bit there).

Our gen­eral styles for ty­pog­ra­phy would be al­ways higher in speci­ficity than the re­sets; our generic ob­jects would al­ways over­ride the ty­pog­ra­phy styles of any com­plex­ity; our com­po­nents would al­ways be guar­an­teed to over­ride the styles of generic ob­jects; and any util­i­ties would al­ways over­ride any­thing else, and all with­out us­ing !im­por­tant. And we could even han­dle things in­side each layer by cre­at­ing sub-lay­ers, to al­low mod­i­fiers for com­po­nents to over­ride their base styles even if those base styles are some­what complex.

Try It Today

Talk­ing about all of this —we can al­ready kinda im­ple­ment our styles this way us­ing noth­ing but the in­creas­ing of speci­ficity avail­able al­ready.

The al­go­rithm would be sim­ple: for each layer we need to cal­cu­late the max­i­mum speci­ficity, then add a num­ber of re­dun­dant match­ing class se­lec­tors to each se­lec­tor in a way they would split all the things into groups.

Let's say we have an ab­stract ob­ject's se­lec­tor: .but­ton:hover, then a com­po­nent .My­Block-Sub­mit, and, fi­nally, a util­ity .is-hid­den. We can leave alone the first layer for the ob­ject, it would be the most bot­tom one. Then we cal­cu­late that it have two class-level se­lec­tors, so we add that num­ber (plus one, to guar­an­tee the over­ride of any pos­si­ble generic se­lec­tor there) to each se­lec­tor of the sec­ond layer of com­po­nents. Then we cal­cu­late the speci­ficity of the com­po­nent layer (here we have just one class ini­tially, in re­al­ity, it would be of­ten much larger, plus the three classes from the pre­vi­ous layer, plus one for re­li­a­bil­ity) and add the cor­re­spond­ing num­ber of re­dun­dant class se­lec­tors to any­thing in util­ity group.

The eas­i­est (and the one method with the most sup­port) way to add the de­sired speci­ficity is pos­si­ble if you have con­trol over the HTML of a page: just add a class con­tain­ing a sin­gle un­der­score to html el­e­ment —<html class="_">, and then use the chains of ._._ be­fore your se­lec­tors. It would look like this:

.button:hover {}
._._._ .MyBlock-Submit {}
._._._._._ .is-hidden {}

The only is­sue that can hap­pen is that one of the se­lec­tors you're pre­fix­ing would have a part that tar­gets a root se­lec­tor. In case of :root or html ones we could rather eas­ily prop­erly at­tach this part to the ac­tual se­lec­tor, for more am­bigu­ous se­lec­tors we'd need to du­pli­cate it like ._._._._.is-hid­den, ._._._._ .is-hid­den, though, if you know that you're do­ing, you prob­a­bly wouldn't want to use any other classes on root.

And here we have all of the lay­ers sep­a­rated in a way their or­der doesn't mat­ter at all and you could make any layer as com­plex as you like with­out the fear of the need to over­ride it later. And yes, you'd still have all the usual CSS speci­ficity rules in­side of lay­ers if you'd want to use them for more gran­u­lar con­trol of things.

Proposal For Native Tools

In this ar­ti­cle, I have shown that it would be al­ready pos­si­ble to set the speci­ficity of any se­lec­tor to any given num­ber from one class to eter­nity. This could be used for more fine-grained con­trol over your li­braries' code and com­po­nents, and in my prac­tice, I had nu­mer­ous oc­ca­sions where it would be tremen­dously helpful.

That's why I pro­pose to add the nec­es­sary tools to na­tive CSS — to al­low de­vel­op­ers to con­trol the speci­ficity. Oth­er­wise, it is pos­si­ble de­vel­op­ers in need would rely on hacks and aw­ful code in the future.

What ex­actly I pro­pose? Cer­tainly not some­thing like a pseudo­class for mod­i­fy­ing the speci­ficity of a given se­lec­tor. That would have too com­plex syn­tax (how would you pass the speci­ficity to it?) and you'll need to use this just any­where when solv­ing your usual CSS prob­lems. That's bad.

What I'd like to see is some kind of a more gen­eral way of con­trol­ling the speci­ficity not for spe­cific se­lec­tors or rules, but for groups of rules. I think of some kind of an @-rule for it, so you could group any num­ber of rules in a “layer”, then some­how de­ter­mine the re­la­tion­ships of those lay­ers be­tween them­selves, and voilà —you'd have a way to con­trol the cas­cade it­self, the thing that al­ways was out of touch when you were de­vel­op­ing your stylesheets.

And the best part —speci­ficity is a part of CSS that is ap­plied only for se­lec­tors, it doesn't de­pend on DOM, on any in­her­i­tance there etc. So there shouldn't be a lot of trou­bles im­ple­ment­ing some­thing that changes the speci­ficity it­self (or cre­ates groups of it like it is al­ready there with the dif­fer­ent parts of the cas­cade).

Conclusion

CSS is fun, and as this ar­ti­cle shows, would be re­ally pow­er­ful with the tools new Specs would pro­vide. There would be in­cred­i­ble things pos­si­ble through hacks. There are al­ready hacks like .foo.foo.foo for mod­i­fy­ing the speci­ficity, and those tools can al­low us to write and reuse the code that is more ef­fec­tive and main­tain­able than ever.

But I'd like to see those things pos­si­ble not through hacks, but us­ing the na­tive CSS. I think this is en­tirely pos­si­ble to im­ple­ment in browsers.