Selector Specificity with CSS Preprocessors

Share this article

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?

Frequently Asked Questions on CSS Preprocessors and Selector Specificity

What is selector specificity in CSS preprocessors?

Selector specificity is a concept in CSS that determines which styles are applied to an element when there are conflicting rules. It’s a system of weights assigned to different types of selectors, such as ID, class, and type selectors. In CSS preprocessors like Sass or Less, this concept remains the same. However, these preprocessors provide additional features like nesting, which can influence the specificity of your selectors.

How does nesting affect selector specificity in CSS preprocessors?

Nesting is a feature in CSS preprocessors that allows you to write more organized and readable code. However, it can also increase the specificity of your selectors. Each level of nesting adds to the specificity, making the nested selector more specific than the parent selector. This can be useful for overriding styles, but it can also lead to unintended consequences if not managed carefully.

How can I calculate the specificity of a selector in CSS preprocessors?

The specificity of a selector is calculated based on the types of selectors used. In general, ID selectors have the highest specificity, followed by class selectors, and then type selectors. Each type of selector is assigned a different weight, and the total specificity is the sum of these weights. Some CSS preprocessors, like Sass, provide functions to calculate the specificity of a selector.

What is the meaning of ‘&’ in CSS styling in preprocessors like Sass?

The ‘&’ symbol is used in CSS preprocessors like Sass to reference the parent selector in a nested rule. This can be useful for creating more specific selectors or for applying styles to different states of an element, like hover or active states.

How can I increase the specificity weight with double selectors in Sass?

In Sass, you can increase the specificity of a selector by duplicating it. This is known as chaining or double selectors. For example, .class.class will have a higher specificity than .class. However, this should be used sparingly as it can make your CSS harder to maintain and override.

What are the potential issues with high specificity in CSS preprocessors?

High specificity can make your CSS harder to maintain and override. It can lead to a situation known as specificity wars, where you have to keep increasing the specificity of your selectors to override previous styles. This can result in bloated and complex CSS.

How can I manage specificity in CSS preprocessors?

There are several strategies to manage specificity in CSS preprocessors. One is to use low-specificity selectors as much as possible. Another is to avoid unnecessary nesting, as this can increase the specificity. You can also use the BEM (Block, Element, Modifier) methodology to keep your specificity low and consistent.

Can I use !important to override specificity in CSS preprocessors?

Yes, you can use the !important rule to override specificity in CSS preprocessors. However, this should be used as a last resort, as it can make your CSS harder to maintain and can lead to specificity wars.

How does the order of selectors affect specificity in CSS preprocessors?

The order of selectors can affect specificity in CSS preprocessors. If two selectors have the same specificity, the one that comes last in the CSS will be applied. This is known as source order specificity.

Can I use inline styles to override specificity in CSS preprocessors?

Yes, inline styles have the highest specificity in CSS and can override other styles. However, they should be used sparingly as they can make your CSS harder to maintain and can lead to a lack of consistency in your styles.

Alexander FutekovAlexander Futekov
View Author

Alexander is a front-end developer currently working at Telerik. The few years he spent studying anthropology and working in the usability domain help him not to forget that code should be written first for people and then for machines. He owns and maintains CSS PRE, a small site that compares the popular CSS preprocessors.

AdvancedCSSbemcss specificityLESSoocsssassStuRStylus
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form