Understanding Cascades and Specificity in CSS

Avalanche Defined Styling.

When you follow a CSS tutorial, you are presented with the simplest situations possible. When you want to change the color to a paragraph, write some pseudo code including color: red and the color changes to red. This is simple enough. However the moment you start creating styling for a whole website, you realize that things don't work the way you want them too. Sometimes you set a color to white, but it shows black. Other times, no matter how much you try, any of the styles you are applying simply refuse to work.

CSS is not a programming language per se. I don't say this to anger CSS programmers, I believe the term programmer is just a title, nothing more. The reason I mention this is because we don't get the luxury of having a good compiler to throw errors when things go wrong. CSS is a styling language. It stands for Cascading Style Sheet. Cascading is the important keyword.

What is Cascading?

What this means is that rules cascade into each other from top to bottom like an avalanche. I can write rules on how a paragraph is supposed to look like, later in my code, I can decide to add more rules without deleting the previous. Here is how it works:

/** Rule 1 **/
.paragraph {
    font-size: 18px;
    color: white;
}

Later down the line I can add more rules to the same paragraph:

/** Rule 2 **/
.paragraph {
    margin-bottom: 20px;
    line-height: 26px;
}

When the browser reads those rules, it will process them in the order they were defined, cascading from top to bottom. Here is what the browser will set as the final rules:

/** Generated Rule **/
final selector {
    font-size:18px;
    color: white;
    margin-bottom:20px;
    line-height:26px;
}

The first rule cascaded into the second. If we were to redefine the same rule with different value, it will be replaced with the latest value. Let's say we add another rule to change the color to red, here is what is going to happen.

/** Rule 3 **/
.paragraph {
    color: red;
}

After processing, this will be the result:

/** Generated Rule **/
.paragraph {
    font-size:18px;
    /* color: white;  this rule will be deleted*/
    margin-bottom:20px;
    line-height:26px;
    color: red;
}

What if we add a color attribute to the first CSS rule?

/** Rule 1 **/
.paragraph {
    margin-bottom:20px;
    line-height:26px;
    color: yellow;
}

The final color will be the last one defined:

/** Generated Rule **/
.paragraph {
    font-size:18px;
    /* color: yellow;  this rule will be deleted for the next*/
    /* color: white;  this rule will be deleted for the next*/
    margin-bottom:20px;
    line-height:26px;
    color: red; /* The last rule defined */
}

This is the cascading part of CSS. Always remember that the last rule defined will be the one that applies. There are exceptions of course, and this has to do with how specific your selector is.

Specificity

In the previous examples we used .paragraph for all the rules. Because they were all the same selector, they had equal weight. But some selectors have more weight then others and there is a hierarchy explicitly defined to know which is more important. Here is the list in order of importance:

#ID > .Class > [attribute]/:pseudo selectors (:hover) > HTMLelement > wildcard *

These do make sense. For example, there should be only one tag with a specific ID. For classes, there will be a group of related elements sharing a class. Html tags are more common, and so on and so forth. Note that Inline style has more weight then IDs.

The weight of the selector bypasses the cascading effect.

/* Rule 1 */
#paragraph {
    color: red;
}

/* Rule 2 */
.paragraph {
    color: green
}

/* Generated Rule *
selector {
    color:red;
    /* color:green; Deleted in favor of the #id selector */
}

In this case, the order of definition did not matter. The ID prevailed.

Very specific rules

In the real world, you don't find code as simple as the above example. People use complex selectors to target a specific kind of element. Here is an example:

.nav ul li p span {
    font-weight: bold;
}

This selector looks very specific, and sometimes is source of lots of problems when you try to modify it later down the line. Let's say we have another rule defined earlier that is a tad more specific:

/* Rule 1 */
.nav ul li .paragraph span {
    color: black;
    font-size:20px;
    font-weight: normal;
}

/* Rule 2 */
.nav ul li p span {
    font-weight: bold;
}

Can you guess if the text inside the <span> element is going to be bold or just plain?

If you said plain, then you were right. The First rule is more specific because it uses a class selector for the paragraph instead of the element tag.

A common source of problem is with the pseudo selectors. Especially the :visited selector. I find it often defined under the general rules of a website where it forces all the visited links to be a certain color. Later down the line when a user tries to modify the color for a specific rule it doesn't work. Always study the specificity of a selector to debug it.

Note that if you tried very hard and still can't properly write your CSS in a way that it works, there is the !important keyword. When used in conjunction with your other rules, it ignores everything and applies the style marked as important.

p {
    color: red !important; 
}

This means, "I don't care, make all my paragraphs red." Unless of course there is another place down the line that you mark as important as well. I don't recommend this keyword unless a kitten's life is in danger.

Naming conventions

It is one thing to try to track how specific a selector is, and it is a whole other to write it correctly on the first attempt. To avoid the hustle, use a naming convention. For example, for this website, I almost never use nested selectors (nav.navigation ul li a:hover). Instead I use a convention called BEM

Instead of writing that complicated navigation link selector I just wrote, I write this:

<a href="#" class="navigation__link">Home</a>

.navigation {
    rules;
}

.navigation__link:hover{
    rules;
}

Much simpler, much more elegant. I don't have to worry about specificity because all my classes are pseudo unique. It is a modularized CSS. All the elements inside the navigation element will carry a class that starts with .navigation__*. This way all I have to worry about is the Cascading part of CSS, which is pretty easy in my opinion.


CSS can become very complicated when it grows in size. The key to make it easier to maintain in the long run is to organize at the very beginning. Use a convention like BEM to make your life easier a year from now when you have to modify your design.

I'm not entirely a fan of using conventions to a T, but I do use it as an inspiration to create something that works for myself. The goal is to make your job easier.

Resources


Comments

There are no comments added yet.

Let's hear your thoughts

For my eyes only