Search code examples
csscss-selectorscss-specificity

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?


Solution

  • 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.