Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nesting CSS selectors without increasing specificity

Let's take these three selectors, sorted from the highest specificity to the lowest:

.special-section p { }
.weird-font        { }
p                  { }

Many CSS gurus recommend against nesting like in the first selector .special-section p, because its specificity is high enough that you can't override it with a simple class like .weird-font. I would like to find a way to still achieve nesting like in .special-section p, but without increasing specificity. Something like this:

 .weird-font { }
 .special-section p /* with hack to decrease specificity */ { }
 p { }

Use case:

It's pretty safe to apply defaults for typography and such document-wide using simple selectors like p. However, I would like to change those defaults for a particular section, similar to .special-section p, without having to use hacks to increase the specificity of selectors like .weird-font. I would rather use a hack to decrease the specificity of .special-section p than use a hack to increase the specificity of .weird-font. Is there a way to do this?

like image 842
Flimm Avatar asked Dec 23 '15 11:12

Flimm


3 Answers

You can't decrease the specificity, but you can add an even more specific selector for the exception.

.weird-font, /* Normal weird font */
.special-section p.weird-font /* Override for special section */ 
  { }

But as you can see, it's a sliding scale. So those gurus are probably right. If you would remove .special-section p, and instead give those P's their own selector .special-section-para or something, then you won't have this problem.

But personally, I don't mind having to add an exception like the one above now and then. I think the whole specifity thing is there for that purpose, and if you need a more specific selector to style something, to me that seems the right thing to do.

A commonly heard solution is to use !important. The problem with !important is that there is only one level of override. If you make a more specific selector, you can override it again with a still even more specific selector. After using !important, you're out of options. More importantly, using !important may interfere with special style sheets that a user may have for increased readability. For that reason I never use !important in a situation like this.

But then again, I don't consider myself a CSS guru. ;)

like image 124
GolezTrol Avatar answered Sep 18 '22 15:09

GolezTrol


These days, in 2018, this is getting close to possible.

First of all, CSS4 will have a way that allows you to create more specific selectors without increasing specificity:

:where(.special-section) p {
    color: red;
}

This will set the paragraph color inside .special-section to red, but with a specificity of 001 (i.e. the same specificity that a plain p selector would have).

The spec still calls this special pseudo-class :something(), but chances are it's going to be called :where(). (Side note: I really want this to be known as the "honey badger selector").

But that's still in the future.

However, there is actually a way to achieve this today, if you don't have to support IE anymore (or are happy with less-than-perfect fallbacks), and that is by using custom properties a.k.a. CSS variables.

So you want this:

.special-section p { color: red; }
.weird-font        { color: magenta; }
p                  { color: green; }

but with the first part having a specificity that's lower than any selector with a class in it. You can do it like this:

.special-section p { --low-specificity-color: red; }
.weird-font        { color: magenta; }
p                  { color: var(--low-specificity-color, green); }

If you run the below snippet in a modern browser, you should notice that the second paragraph is red, because it's in a special section, but the third paragraph is magenta, because it's .weird-font -- even though .weird-font has 010 specificity and .special-section p has 011.

.special-section p { --low-specificity-color: red; }
.weird-font        { color: magenta; }
p                  { color: var(--low-specificity-color, green); }
<p>This is a paragraph.</p>

<section class="special-section">
   <p>This is a paragraph inside a special section.</p>
   <p class="weird-font">This is a paragraph with a weird font inside a special section.</p>
</section>

   <p class="weird-font">This is a paragraph with a weird font.</p>

   <div class="weird-font">This is a div with a weird font.</div>

This works because while the --low-specificity-color is changed with 011 specificity, it is only applied with a 001 specificity.

like image 21
balpha Avatar answered Sep 18 '22 15:09

balpha


As a CSS guru, I bemoan the idea of throwing out everything Selectors has to offer just to avoid specificity issues. That's not to say I don't believe the specificity mechanic is flawed, but surely there are less dramatic workarounds for it.

First off: no, you can't decrease the specificity of a selector. Selectors doesn't provide any features with negative specificity levels that would decrease specificity in such a manner. The lowest you can go is *, which has zero specificity (i.e. it does not make a complex selector any more or less specific).

So your only recourse on the selector level is to increase it. Whether you can do this without using hacks depends on your definition of "hack".

The following is what I would consider a hack, because it makes use of a syntactically legal but semantically nonsensical selector like :not(_) that has no obvious purpose but to add a type selector's worth of specificity to a complex selector (which is far from obvious especially to the uninitiated):

.special-section p { }
.weird-font, :not(_).weird-font { }

The following is not what I would consider a hack, because it's something you would do normally anyway. Pretty much the only "issue" with it is that it's an apparent repetition of the lone class selector:

.special-section p { }
.weird-font, .special-section p.weird-font { }

If you consider any sort of extraneous selector for the sake of increasing specificity a hack — which is a perfectly reasonable POV, make no mistake — then the next best thing that isn't a hack is !important.

Personally, I would choose a specificity hack. !important has, ahem, important repercussions that don't come with a specificity hack — remember that !important and specificity have different semantics. For example, you cannot override an !important declaration with an inline style or JavaScript unless they are marked important as well.1


1In fact, this was my response to Lea Verou when she had a discussion on Twitter some time ago regarding specificity hacks versus !important.

like image 25
BoltClock Avatar answered Sep 20 '22 15:09

BoltClock