Nesting CSS selectors without increasing specificity
Asked Answered
C

4

13

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?

Cedar answered 23/12, 2015 at 11:6 Comment(7)
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.Overspend
Adding classes and then selecting by them is not a hack; it's the defined way of selecting elements. The use case could be made clearer if you had some example markup.Treadwell
@TylerH: That's not what the OP is referring to. You don't really need example markup to infer that in the example CSS, p and .weird-font refer to the same element.Handcart
@Handcart I have to disagree; it's totally unclear to me what OP's markup would look like. Even if, through great effort, we could make an assumption (which all the answers do), it would be made much clearer and easier to understand (and therefore helpful to others) with a simple markup example. Further, if we know the markup, we can offer a potential alternative way to achieve OP's goal. The accepted answer, for example, does the exact opposite of what the OP wants (it increases specificity where he wants to avoid increasing specificity).Treadwell
@TylerH: I don't deny that there is always room for improvement, but it doesn't take "great effort" to make such an assumption considering the only situation in which specificity even applies is when both selectors are referring to the same element. Both the accepted answer and my own suggest increasing specificity rather than decreasing it because you can't decrease it, and there isn't any other solution to the OP's problem that doesn't take a dependency on the markup (and, evidently, the OP isn't all that interested in a solution that does, either, which is fine).Handcart
@TylerH: The accepted answer starts with the sentence "you can't decrease the specificity", so I consider the question answered. If someone can show me a way to decrease the specificity I'll accept that answer instead and downvote the answers that say it's impossible.Cedar
@Cedar My comments refer to an alternate approach to achieve what you want rather than determining whether or not it's possible to "decrease specificity" (sort of misnomer of a phrase in the first place). Analogously, suggesting you fly across the ocean instead of trying to see if you can somehow breathe underwater for a 5,000 mile swim or if you'll just have to be OK with surfacing every couple feet to breathe.Treadwell
R
8

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.

Renewal answered 12/7, 2018 at 13:18 Comment(3)
:something (or whatever the name is going to be) is now top of my list of things I am anticipating in tech!Cedar
By the way, why the "honey badger selector". I get how it's the opposite of !important, but I don't get the link.Cedar
This is now available on modern browsers! caniuse.com/mdn-css_selectors_whereCedar
O
8

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. ;)

Overspend answered 23/12, 2015 at 11:10 Comment(0)
R
8

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.

Renewal answered 12/7, 2018 at 13:18 Comment(3)
:something (or whatever the name is going to be) is now top of my list of things I am anticipating in tech!Cedar
By the way, why the "honey badger selector". I get how it's the opposite of !important, but I don't get the link.Cedar
This is now available on modern browsers! caniuse.com/mdn-css_selectors_whereCedar
H
7

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


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

Handcart answered 23/12, 2015 at 11:22 Comment(5)
On a side-note, am I the only one who finds discussions on Twitter unreadable? For instance, in the discussion you linked to, I can see @LeaVerou replying to you saying "excellent point", but I can't see what you originally said! imgur.com/lUzfW7gCedar
@Flimm: Ha, that's why I said my response to her was what's given in my answer :) It's not you - I made my account private because I stopped using Twitter but I was still getting spam notifications. Not fun :(Handcart
AFAIK you can't override a JavaScript-inserted !important property with any kind of CSS. You need more JavaScript to do that.Treadwell
@TylerH: Yeah - I was specifically referring to a CSS-level !important declaration.Handcart
@Handcart Ah, the "or JavaScript" bit threw me off :-)Treadwell
V
0

I like to be as specific as is currently necessary. I do like to leave room for future CSS changes, so don't go as specific as possible for the sake of it, such as:

.great-grandfather .grandfather .father .child { }

I will if I have to of course. But taking the example above, if I wanted to override the .child for a particular element that uses this class which has styling which might be like this:

.child {
   color: black;
}

I'd go one parent above to override it, if possible:

.father .child {
   color: white;
}

Further down the line, if an element on a particular page uses the .child class and in this case I need to override both the .father .child, I'll go one more level of specificity:

.grandfather .father .child {
   color: red;
}

Doing it this ensures you don't need to use !important... Which I avoid like the plague as much as possible!

Vulvitis answered 23/12, 2015 at 11:22 Comment(1)
So is there a way to achieve the same effect as .parent child selector but with lower specificity?Cedar

© 2022 - 2024 — McMap. All rights reserved.