Dealing with the Cascade and Specificity
One of the things that I enjoy covering in my workshops is how to deal with managing the CSS cascade. We all have different techniques that we use and each come with their pros and cons.
The cascade is the rule-based system that rendering engines use to decide what properties should apply to an element when multiple rules are declared for the same element.
For example, let’s say you have a header with a navigation bar. The header is light text on a dark background. As you hover over the navigation, a dropdown appears. It features dark text on a white background. Both the navigation and the dropdown have links.
The HTML structure could look like this:
<nav>
<ul class=“navigation”>
<li><a href=“#”>Home</a></li>
<li><a href=“#”>About</a>
<ul class=“dropdown”>
<li><a href=“#”>Company</a></li>
<li><a href=“#”>Hiring</a></li>
</ul>
</li>
<li><a href=“#”>Contact</a></li>
</ul>
</nav>
The elements within the dropdown will, by default, inherit color and font properties from the header. Not a big deal, since we’re going to redefine all of those properties for the dropdown.
.dropdown a { color: black; background-color: white;}
.navigation a { color: white; background-color: black; }
Excellent! All links in the dropdown are defined and all the links in the navigation are defined.
But did you notice something?
Specificity
Both rules have the same specificity. And since the dropdown is in the navigation, both of these rules apply to the dropdown. And since the navigation rule is declared after the dropdown rule, the dropdown will be white text on a black background. Boo.
There are, as they say, many ways to skin a cat. I’ve never actually skinned a cat, nor do I plan to, but it’s a saying. I don’t know why. Ahem.
Let’s fix this.
Rearranging CSS
The first thing we can do is simply rearrange the CSS such that the dropdown rule appears after the navigation rule. This’ll fix the specificity and we’ll be all set.
Within your project, you’d have to make sure that CSS for any HTML deeper in the DOM tree is declared after that which is higher up the DOM tree. Depending on the complexity of your project, this might not be a big deal.
At Yahoo!, for example, we did conditional loading of component CSS. That meant that blocks of CSS could load in a different order. Therefore, this wouldn’t work.
Increase Specificity
We can increase the specificity of the dropdown CSS. We can do this by doubling up on selectors.
.dropdown.dropdown a { color: black; }
.navigation a { color: white; }
The first is a bit hacky but it works. Mind you, if you styled #navigation a
then no amount of .dropdown.dropdown.dropdown
will help you.
Or we can pull out the dreaded !important
!
.dropdown a { color: black !important; }
.navigation a { color: white; }
That works a treat. Let’s all go home!
Well, until the day when you have other links inside dropdowns that need to be a different colour and suddenly you’re throwing !important
on everything.
Use Child Selectors
We can use child selectors. This is something I advocate whenever I do a SMACSS workshop. A child selector limits the impact since it doesn’t target every descendent element; it only targets the direct child element, which were the only ones that you actually wanted that colour, anyways.
.dropdown > li > a { color: black; }
.navigation > li > a { color: white; }
Depending on the complexity of your HTML, this can result in longer selectors to target the elements you care about, such was the case here where we had to go through the list item to get to the link.
If your component has a deeper hierarchy of elements, your selectors could get out of hand. Tangentially, if you have a deep component hierarchy, that might be an opportunity to reconsider the design of your component.
BEM Classes
In getting more meaningful CSS, maybe each link style should have its own class.
.dropdown__link { color: black; }
.navigation__link { color: white; }
This certainly works a treat. Our specificity is low and manageable. Our selectors are succinct. We’ve put the burden on declaring a class name on every single link in both the dropdown and the navigation. A bit tedious but templating can make this moot.
<nav>
<ul class=“navigation”>
<li><a href=“#” class=“navigation__link”>Home</a></li>
<li><a href=“#” class=“navigation__link”>About</a>
<ul class=“dropdown”>
<li><a href=“#” class=“dropdown__link”>Company</a></li>
<li><a href=“#” class=“dropdown__link”>Hiring</a></li>
</ul>
</li>
<li><a href=“#” class=“navigation__link”>Contact</a></li>
</ul>
</nav>
Atomic Classes
We can decide to turn things on their head and instead of considering the dropdown and navigation as components and styling within that context, we can consider that we have links that need to be styled and just style those directly without thinking about it being a component.
Atomic CSS uses separate classes for each property to be applied. We have 4 different property/value pairs that we want to apply.
<nav>
<ul>
<li><a href=“#” class=“C(#fff) Bgc(#000)”>Home</a></li>
<li><a href=“#” class=“C(#fff) Bgc(#000)”>About</a>
<ul>
<li><a href=“#” class=“C(#000) Bgc(#fff)”>Company</a></li>
<li><a href=“#” class=“C(#000) Bgc(#fff)”>Hiring</a></li>
</ul>
</li>
<li><a href=“#” class=“C(#fff) Bgc(#000)”>Contact</a></li>
</ul>
</nav>
At least in our simplistic example, the HTML isn’t any bigger than it was before than with BEM-style classes. Here’s what the Atomic CSS looks like:
.C\(\#000\) { color: black; }
.C\(\#fff\) { color: white; }
.Bgc\(\#000\) { background-color: black; }
.Bgc\(\#fff\) { background-color: white; }
Skinned
There’s no right or wrong way, but I’m admittedly and unsurprisingly biased towards a SMACSS-based approach, which means either picking classes or child selectors where appropriate.
Trying to win the specificity war by increasing specificity is just going to make your life increasingly more difficult with each new component you add to the site.
Conversation
That was awesome. Finally I was able to clarify the specificity. Thanks, Snook.
I wonder what your ideas are in regards to CSS modules. I find keeping my CSS scoped to a single module (no element selectors, except for a global reset) & within modules my specificity relatively flat, makes maintenance a breeze.
@Joost: That mirrors the BEM approach and aligns well with SMACSS. Build self-contained components. With SMACSS, I leave the door open to using child selectors but agree that the CSS should be scoped to just the component.
I have been doing Styleguide based CSS for 5+ years and I never knew you could double a class (even if it doesn't exist) to increase the specificity!
Definitely feels like a hack, but good to know :)
What about ENCSS approach ? Seems appropriate.