Control the specificity and order of styles with CSS Cascading Layers

CSS specificity problem

Most of us have been in a situation where we face CSS style conflicts when we write new styles. This may be because of CSS specificity, inheritance, or the use of !important.

CSS uses specificity to decide which styles are applied to which element and their preference. Styles with higher specificity override styles with lower specificity. This is the order of specificity -

!important > id > class > element name

As the codebase grows, it becomes difficult to manage styles leading to unpredictable issues.

Let’s check out an example.

In the below code, the styling for the button is as expected if the .btn class appears before .success.

  <div class="action-btn">
    <button class="btn">Click</button>
    <button class="btn success">Submit</button>
  </div>
 
  .btn {
    background-color: white
  }
  .success {
    background-color: green
  }

What if we have another version of the success button custom-success?

Due to some reasons, we cannot write the styling after .btn.

  <div class="action-btn">
    <button class="btn">Click</button>
    <button class="btn success">Submit</button>
    <div class="custom-actions">
      <button class="btn custom-success">Custom Submit</button>
    </div>
  </div>
 
.custom-success {
  background-color: blue;
}
.btn {
  background-color: white;
}
.success {
  background-color: green;
}
 

In this case, the styling would be disturbed. The button with the class custom-success will get a background color as ‘white’. This is because the btn class overrides the styling of the class custom-success.

Over the years various methodologies were designed to solve this problem. One of them is adding a parent class and increasing the specificity of the styles.

  .custom-actions .custom-success {
    background-color: skyblue;
  }
  .btn {
    background-color: white;
  }
  .success {
    background-color: green;
  }

However, this solution is not 100% effective.

The new CSS Cascading layers feature helps to solve such problems.

Let’s apply the Cascading layers to our example.

We have defined two layers default and actions one after the other. The styles defined in the actions layer get more priority over the ones in the default layer.

  @layer default {
    .btn { 
      background-color: white;
    }
  }

  @layer actions {
    .success {
      background-color: green;
    }
    .custom-success {
      background-color: skyblue;
    } 
  } 

This way, we can always ensure that the custom styles will always take priority over the default styles.

Now, let’s dive deep into the Cascading Layers.

What are Cascading layers?

CSS Cascading layers are a new addition to CSS to solve tricky problems of specificity. They offer more control to the developers to write styles avoiding specificity or source-order conflicts.

Let’s check out the syntax and the usage of Cascading layers.

Syntax

@layer layer-name {rules}

@layer layer-name;

@layer layer-name1, layer-name2, layer-name3;

@layer {rules}

where:

layer-name is the name of the cascade layer

and

rules is the set of CSS rules in the cascade layer

There are 4 ways to create cascade layers.

First way

The first way is to create a named cascade layer with the CSS rules.

  @layer layer-name {rules}
  @layer default {
    .btn { 
      background-color: white;
    }
  }
Second way

The second way is to create a named cascade layer without assigning any styles.

  @layer default;
  //Multiple layers
  @layer default, actions;

In this manner, we define the layer at the top of the CSS file, and the styles can be added later. We can define multiple layers and arrange them according to priority.

NOTE: If multiple layers are defined the last layer takes priority over earlier ones.

Let’s revisit our previous example with layers.

    @layer default, actions;
    @layer default {
      .btn { 
        background-color: white;
      }
    }
    @layer actions {
      .success {
        background-color: green;
      }
      .custom-success {
        background-color: skyblue;
      } 
    } 
  

The Layer actions is given more precedence over layer the ‘default’ layer, so the background color is green and skyblue for the ‘Submit’ and ‘Custom Submit’ buttons respectively.

Changing the order of layers.

  @layer actions, default;
    @layer default {
      .btn { 
        background-color: white;
      }
    }
    @layer actions {
      .success {
        background-color: green;
      }
      .custom-success {
        background-color: skyblue;
      } 
    } 
  

The layer order is changed. The ‘default’ layer takes priority over the actions layer. So the background color of all the buttons is changed to white.

Third way

The third way is to create a cascade layer with no name which created an anonymous cascade layer. Styles cannot be added later to the anonymous layers.

  @layer {rules}
  @layer {
    .btn { 
      background-color: white;
    }
  }

Fourth way

The fourth way is by using @import.

  @import "stylesheet-name.css" layer(layer-name)
  @import "reset.css" layer(reset);

In the example above, we imported reset CSS directly, gave it a layer designation, and specified its priority. By doing so, we consolidated more code into a single file and made CSS easier to manage.

Nested Layers

Layers may be nested to create additional hierarchies and priorities inside the main layers.

    @layer container {
      @layer subheader {
        .sub-header: {
          font-size: '12px';
        }
      }
    }
  

To append rules to the subheader layer inside the container, join the two names with a ..

@layer container.subheader {
  .sub-header: {
    font-color: grey;
  }
}

Use of !important in layers

!important works against the concept of layers. If we are trying to override a class with a layer and !important is already present on the selector, CSS gives priority to the element with the !important keyword.

Let’s check out the below example.

The background color of the button will be white because of the !important keyword, even though the ‘actions’ layer has more priority.

 @layer default {
    .btn { 
      background-color: white !important;
    }
  }
  @layer actions {
    .success {
      background-color: green;
    }
    .custom-success {
      background-color: skyblue;
    } 
  } 
  

Now, consider a scenario in which both the layers have the !important keyword added and the ‘actions’ layer has more priority over the ‘default’ layer.

 @layer default {
    .btn { 
      background-color: white !important;
    }
  }
  @layer actions {
    .success {
      background-color: green !important;
    }
    .custom-success {
      background-color: skyblue !important;
    } 
  } 

Again,

the button background color will be rendered as ‘white’.

The reason is !important works in the opposite way of normal layers. Since the ‘default’ layer is defined before the ‘actions’ layer, all !important styles in the ‘default’ layer will override any styles, including !important styles, from the ‘actions’ layer.

Which one wins between non-layered and layered styles?

When we have CSS with a combination of layered and non-layered styles, non-layered styles take priority over layered styles.

In the below example, we have an anonymous layer defining style for .btn followed by non-layered styles for .btn.

The button will have background-color as ‘skyblue’.

 @layer default {
    .btn { 
      background-color: white;
    }
  }
  .btn {
    background-color: skyblue ;
  }

Even if we change the order, the style remains the same.

In other words, non-layered styles win over layered styles.

Browser Compatibility

Compared to other CSS3 properties, CSS layers have excellent browser compatibility. With various browsers, it has 87.57% compatibility.

Need help on your Ruby on Rails or React project?

Join Our Newsletter