HTML & CSS
Article

Selector Specificity with CSS Preprocessors

By Alexander Futekov

Selector specificity is a real problem for most medium and large-sized projects, and as any other frequently recurring coding problem it needs to be addressed carefully. Even CSS-Tricks recently had an article on how to keep your CSS specificity low.

And before you even try to say !important let me remind you that:

CSS specificity isn’t that complex, but the community has done a lot to make it as easy to comprehend as possible, writing guides by using analogies with fish and Star Wars, or by using poker terminology. There are interactive calculators available online, and even a specificity mixin for Sass, allowing you to check and output the exact specificity value of a selector.

Simpler is better when in comes to a CSS specificity strategy, the specificity workarounds in this article (which may feel a bit hacky) are suited for cases where the architecture doesn’t allow for a simple fix. Use your judgment when deciding which approach suits your project best and ultimately try to hit the perfect balance between clean and maintainable CSS.

Approach #0 – BEM

BEM is more than a naming convention, it is a front-end toolkit invented by Yandex, the philosophy of which is to get closer to object-oriented programming. In practice, this means using a class name for every single thing that you style. Although “no cascading in Cascading Style Sheets” may sound preposterous to some, the idea to never use type selectors and to avoid nesting is extremely helpful when creating modules that should be maintainable, portable, self-sufficient, easy-to-modify, and scalable.

One of the main benefits of the BEM methodology though is that it keeps specificity the same for every selector, so there should be practically zero issues caused by overly specific selectors, this is why this approach is #0 – if you adopt it you are essentially eliminating any future problems with selector specificity in a project.

Specificity of Rules

Since you’ll be using only single classes for everything you write, every single selector will have a specificity of 0,1,0

When BEM isn’t the answer

There are countless success stories of front-end architectures that use BEM, however, as popular as BEM may be, it is not suited for a specific type of projects. It seems to work great for projects where front end developers are the only people that write the HTML. If you do however write for a CMS where content editors without any HTML skills write at least some of the content then BEM will be severely limiting.

BEM at its pure form does not allow for nesting and type selectors such as .Section--dark > a (to give it a light color for example), instead it requires you to invent a class for the anchor tag. This leads to a problem – a content editor will insert a default link through a graphic interface which may result in hardly-visible links. This may also apply to all paragraphs, lists, or images within a specific section. Sometimes being able to write pure HTML content without classes is needed, in such cases the solution is having it all style itself by a parent with descendant selectors. This use of cascading allows for contextual flexibility – the presence of custom styles for something when it’s in a different context.

So we need an actual solution for when we can’t use pure BEM, which is quite often. CSS preprocessors can help us here, and all 3 of the popular ones – LESS, Sass, and Stylus are able to ease our job when we need to override a selector with a more specific one.

Approach #1 – Prepend with a selector

When you encounter a specificity problem and want to make a certain selector heavier you can prepend the existing selector with a class, attribute, id, or a type selector. In this way you can bump the specificity just a bit. While this feature has mostly been used to target IE with a conditional html tag, it has a lot more power that awaits to be harnessed.

The parent reference selector (&) in CSS preprocessors easily allows us to prepend a selector to make it heavier, thus we can do the following:

.class {
  body & {
    foo: bar;
  }
}

Resulting in this CSS

body .class {
  foo: bar;
}

You might want to prepend it with the html or body tag, but you can just as well use something more specific that is present on all your pages such as .page, #wrapper, html[lang], etc.

Sass allows you to put the prepending selector within a variable in case of future changes, additionally, for larger projects it might be worth creating a set of prepending selectors with different weights:

$prepend1: "html &";
$prepend2: "#wrapper &";

.selector {
  #{$prepend1} {
    background: #cacaca;
  }
  #{$prepend2} {
    background: #fefefe;
  }
}

Which will result in:

html .selector {
  background: #cacaca;
}
#wrapper .selector {
  background: #fefefe;
}

Other popular preprocessors such as LESS and Stylus also offer this functionality.

Specificity of Rules

Prepending our initial class selector 0,1,0 with a type selector will result in 0,1,1, prepending with an id – 1,1,0.
The drawback here is that once you prepend something with your heaviest selector (i.e. #wrapper) you can no longer override it until you invent a new, heavier selector, which isn’t always possible.

Approach #2 – Self-chained selectors

Prepending a selector is useful but it is not an infinitely scalable solution – you may have limited number of ways to target a parent. Also, once you prepend more than one selector with the heaviest option in your code you limit your options to resolve a specificity problem later on (especially if you’ve used an id).

The front-end community has recently been reminded of one very useful CSS feature – self-chaining a selector to increase its specificity. Self-chained selectors work with ids, classes, attribute selectors, but not type selectors. Still, if you use predominantly classes to style your content self-chaining offers you an infinitely scalable way to override any selector.

With the help of the parent reference selector you can easily chain the same selector to itself multiple times (this applies to ids, classes, and attribute selectors, but not type selectors). You will need to interpolate every & after the first though and the minimum requirement is Sass 3.4:

.selector {
  &#{&} {
    background: #cacaca;
  }
  &#{&}#{&} {
    background: #fefefe;
  }
}
.selector.selector {
  background: #cacaca;
}
.selector.selector.selector {
  background: #fefefe;
}

Once again, you can also do this with LESS and Stylus.

If this is too ugly for your taste you can always create a mixin that can iteratively increase the specificity of any single selector. This mixin uses some advanced features and also requires Sass 3.4:

@mixin specificity($num: 1) {
  $selector: &;
  @if $num > 1 {
    @for $i from 1 to $num {
      $selector: $selector + &;
    }
    @at-root #{$selector} {
      @content;
    }
  }
  @else {
    @content;
  }
}

.selector {
  @include specificity(2) {
    background: #cacaca;
  }
  @include specificity(3) {
    background: #fefefe;
  }
}

Resulting in the following CSS:

.selector.selector {
  background: #cacaca;
}
.selector.selector.selector {
  background: #fefefe;
}

You can create an identical mixin in Stylus, unfortunately there are no easy ways to create such a mixin in LESS.

Specificity of Rules

Self-chaining will increase the specificity of a selector from 0,1,0 to 0,2,0 to 0,3,0 and so on, making it almost infinitely scalable.

Final Thoughts

The natural next step in dealing with specificity problems in our smarter-than-ever CSS would be creating a way to identify conflicting declarations, calculate the specificity of each of the entities with something like David Khourshid’s Sass mixin and then automatically using one of the approaches above to bump up the specificity. Maybe my dreams for self-aware stylesheets are too optimistic, but I think that as CSS preprocessors evolve, the sophisticated logic in our code will increase.

Which of the approached above would you use next time you need to battle with specificity issues? What other strategies do you use to solve your specificity problems?

No Reader comments

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Front-end, once a week, for free.