Day 37: cascade layers

posted on

It’s time to get me up to speed with modern CSS. There’s so much new in CSS that I know too little about. To change that I’ve started #100DaysOfMoreOrLessModernCSS. Why more or less modern CSS? Because some topics will be about cutting-edge features, while other stuff has been around for quite a while already, but I just have little to no experience with it.


Cascade layers introduce a new way of managing specificity in CSS.

Let’s say we’re using a combination of a tag and an attribute selector for styling e-mail input fields. This declaration is part of our base stylesheet and comes early in the stylesheet. Later in the document, we want to use a class to overwrite parts of the base styling:

input[type="text"],
input[type="email"] {
  border-color: hwb(0 0% 0%);
  border-style: solid;
  border-width: 3px;
}

.form-item {
  border-color: hwb(120 0% 40%);
}

This won’t work because input[type="email"] is more specific than .form-item.
There are several ways to work around that.

Using !important

We could use !important, but we all know that this probably isn’t the best idea in the long term.

input[type="text"],
input[type="email"] {
  border-color:hwb(0 0% 0%);
  border-style: solid;
  border-width: 3px;
}

.form-item {
  border-color: hwb(120 0% 40%) !important;
}

Increasing selector specificity

We could increase the specificity of the second selector.

input[type="text"],
input[type="email"] {
  border-color:hwb(0 0% 0%);
  border-style: solid;
  border-width: 3px;
}

.form-item.form-item {
  border-color: hwb(120 0% 40%);
}

This works, but isn't the most beautiful solution either.

Decreasing selector specificity

We could decrease the specificity of the first selector.

input:where([type="text"], [type="email"]) {
  border-color:hwb(0 0% 0%);
  border-style: solid;
  border-width: 3px;
}

.form-item {
  border-color: hwb(120 0% 40%);
}

Using :where() to decrease specificity is a nice solution, and it works great if you only have a handful of selectors, but if you have a group of different selectors that you want on the same level in terms of specificity, there’s a more convenient way to do that.

Cascade layers

Cascade layers give us more control over the cascade. Using the @layer at-rule we can establish our own layers of the cascade. The rules of specificity we already know still apply within each layer, but there are no conflicts between rules in different layers because rules in a layer with higher priority always* win over rules in a layer with lower priority no matter how specific selectors are.

*Okay, not always, but we'll talk about that in another post.

@layer base {
  input[type="text"],
  input[type="email"] {
    border-color:hwb(0 0% 0%);
    border-style: solid;
    border-width: 3px;
  }
}

@layer component {
  .form-item {
    border-color: hwb(120 0% 40%);
  }
}

The specificity of .form-item is still lower than the specificity of input[type="email"], but .form-item is in the component layer, which comes later in the document and thus overwrites styles in the base layer.

There’s a lot more to say about cascade layers, but I’ll save that for later. :)

See on CodePen

Further reading

Do you want to learn even more awesome CSS?

I'm running a workshop with my friends at Smashing Magazine in which I introduce you to the most useful modern features in CSS and show how you can implement them today in your code base to improve scalability, maintainability, and productivity.

Learn more about the workshop!

Overview: 100 Days Of More Or Less Modern CSS