frontend.fyi

So !important to know — CSS Cascade Layers are awesome!

!important has always been the evil of frontend. With css cascade layers, we finally have a new (and better!) way to battle specificity!

🧑‍💻 🎥
... Almost ready!

An !important shift in how we write CSS

!important is probably known to most of us. Most of you probably don’t have the best memories of it. Using !important in your code, will pretty much always result in horror down the road. Over the years there’s been multiple options to avoid using !important in your code. Think about CSS methodologies like BEM, preventing nesting of your CSS selectors, or using general utility classes for everything.

However, even in these cases there might be situations which result in more specific CSS selectors. And then you might be tempted to use !important again. Perhaps because you don’t know how to solve it better, perhaps because you’re on time pressure. Or because you start using an external libary that introduces very specific styles, and you want to override something.

Today is the day all of that changes

With CSS Cascade Layers we finally have a different way to solve this! And the good thing: It works in all major browsers today!

What are CSS Cascade Layers?

CSS Cascade Layers are not totally new to CSS. In fact, browsers have been using this for years already, probably without you knowing it. The layer your browser is already using for a long time is called the user agent layer. Any styles the browser applies by default, it applies in this layer. And it works in such a way, that if you style a similar element with your own CSS, your CSS will always win. Even if the browser’s default style would have a higher specificity.

With cascade layers, you now get the option to create your own layers. On top of that, you can decide which layer wins over which layer, by specifying a specific order. Take a look at the following code:

1
@layer layer1 {
2
h1 {
3
margin-bottom: 20px;
4
}
5
}
6
7
@layer layer2 {
8
h1 {
9
margin-bottom: 30px;
10
}
11
}

The above code defines two layers. Both specify a text color on the h1. When parsing the CSS, the layer that’s defined the last, will win in specificity. In this example that might seem fairly logical, because the second layer is also last in the file. Similar to how CSS works.

You are however able to define the layers at the top of your CSS file, without adding content to it like so:

1
@layer layer2, layer1;
2
3
@layer layer1 {
4
h1 {
5
color: red;
6
}
7
}
8
9
@layer layer2 {
10
h1 {
11
color: blue;
12
}
13
}

The first time the browser encounters a layer definition, defines the order of importance. So in this case, the layer1 style will win!

Now let’s say our heading also has a class of heading, and our stylesheet looks as follows:

1
@layer layer2, layer1;
2
3
@layer layer1 {
4
h1 {
5
color: red;
6
}
7
}
8
9
@layer layer2 {
10
.heading {
11
color: blue;
12
}
13
}

What do you think will happen? The .heading selector has a higher specificity, and thus more ‘important’. However, since we tell the browser layer1 comes after layer2, the h1 will win! The order of the layers makes this style more important! This is a very powerful concept, and it allows us to write CSS in a totally different way.

It might be a bit harder to grasp at first, but it makes writing your styles so much easier.

Winning the specificity war with cascade layers

Let’s say you have the following code:

1
<button class="btn">Click me</button>
2
<button class="btn">Click me too!</button>
1
.btn + .btn {
2
margin-left: 10px;
3
}

This style will make sure that if there’s two buttons next to each other in the DOM, it will give the second button a margin left. However, what if you want to override this margin in a specific case. Let’s say you add a class ml-24 to the second button:

1
<button class="btn">Click me</button>
2
<button class="ml-24 btn">Click me too!</button>

You update your styles accordingly:

1
.btn + .btn {
2
margin-left: 10px;
3
}
4
5
.ml-24 {
6
margin-left: 24px;
7
}

The result? Still a gap of 10 pixels! 🤯 The reason is, because the .btn + .btn selector has a higher specificity than .ml-24, and thus wins.

Annoying right! You just want to override the margin for this specific case! Well, with cascade layers you can! Let’s take a look:

1
@layer base, utilities;
2
3
@layer base {
4
.btn + .btn {
5
margin-left: 10px;
6
}
7
}
8
9
@layer utilities {
10
.ml-24 {
11
margin-left: 24px;
12
}
13
}

Now, the .ml-24 class is defined in a different layer than the .btn + .btn selector. And we tell the browser that the utilities layer comes after the base layer. No matter the specificity, the utilities layer will always win! And thus, the margin will be 24 pixels!

This is SO powerful!

What about using external libraries?

Have you ever used an external libary, and you wanted to override a specific style? Only discovering that they wrote styles with such a high specificity that you couldn’t override it unless you added !important to your own styles? Yup, I’ve been there too…

Now with cascade layers, that problem is solved as well! The only thing you need to do, is add the library’s styles to a different layer than your own styles. Like so:

1
@layer vendors, base, utilities;
2
@import "any/vendor/css/file.css" layer(vendors);
3
4
@layer base {
5
.btn {
6
color: red;
7
}
8
}
9
10
@layer utilities {
11
.btn {
12
color: blue;
13
}
14
}

This will make sure that the vendor styles are applied first, and then your own styles. No matter the specificity in the vendor library, your own styles will always win!.

I wish I had this years ago!

Don’t forget to check the video

Make sure to check out the video as well, where I show you a live demo of all of this!