7. Layers
Sometimes two CSS rules try to style the same element and contradict each other. One says a text should be red and another that it should be blue. Which one wins? CSS has to choose one, and it is not always easy to guess which.
Cascade layers, or CSS Cascade Layers, help us bring order. They let us group styles into named blocks, such as reset or componentes, and clearly decide which block rules over which.
The idea is simple: you choose the order of the layers and, when two rules clash, the one in the highest-priority layer always wins. No more, no less. Let's see it step by step.
Declaring the order
The @layer rule is used. The first thing, and most advisable, is to declare the order of the layers at the beginning of the style sheet:
@layer reset, base, componentes, utilidades;
With that line we decide the priority once and for all: reset is the one with the least weight and utilidades the one with the most. The later a layer is declared, the more it rules.
Then we fill each layer with its styles:
@layer reset {
* {
margin: 0;
padding: 0;
}
}
@layer base {
body {
font-family: sans-serif;
color: #333;
}
}
@layer componentes {
.tarjeta {
border: 1px solid #ccc;
padding: 1rem;
}
}
Notice an important detail: the order is set by the first declaration, not by where you write the styles. Even though the @layer base block comes before @layer componentes in the file, the priority was already decided in the initial line.
Order rules over specificity
Let's see the golden rule in action. I have a paragraph to which I apply red color through an id (very specific) in the base layer, and green color through a class (not very specific) in the tema layer:
@layer base, tema;
@layer base {
#aviso {
color: red;
}
}
@layer tema {
.aviso {
color: green;
}
}
<p id="aviso" class="aviso">
Even though the id is more specific, I win because I am in a later layer.
</p>
Even though the id is more specific, I win because I am in a later layer.
The text comes out green. The class in the tema layer beats the id in the base layer simply because tema was declared afterwards. Specificity has stopped mattering between layers.
Adding styles to an existing layer
A named layer does not close. You can go back to it as many times as you want and the styles accumulate without changing its priority:
@layer utilidades {
.margen-1 {
margin: 1rem;
}
}
/* Later, even in another file */
@layer utilidades {
.centrado {
text-align: center;
}
}
This is very useful when I split the styles across several files: each one contributes to the same layer without stepping on the others.
Nested layers
We can create layers inside layers using a dot. This way an external library can have its own sublayers without clashing with ours:
@layer framework {
@layer layout {
main {
display: grid;
}
}
}
/* Equivalent to pointing directly at the sublayer */
@layer framework.layout {
aside {
width: 20rem;
}
}
Importing inside a layer
When we bring in an external style sheet, we can place it entirely into a layer with the layer() function:
@import "normalize.css" layer(reset);
@import "bootstrap.css" layer(framework);
This way, no matter how aggressive a library's specificity is, your styles override it just by placing it in an earlier layer. No more fighting against third-party CSS.
Styles without a layer always win
Watch out for this, because it is confusing at first. Any style that is not inside a layer has more priority than all those that are, no matter how many layers you have:
@layer base {
p {
color: green;
}
}
/* This one wins, because it lives outside any layer */
p {
color: red;
}
The paragraph comes out red. You can think of styles without a layer as forming an invisible layer that is always the last, that is, the most powerful. That is why it is a good idea to put almost everything in layers and leave outside only what you want to rule above everything else.
!important turns everything around
With !important, the order of the layers is reversed. The first declared layer becomes the strongest, and styles without a layer become the weakest:
@layer base, tema;
@layer base {
p {
color: green !important;
}
}
@layer tema {
p {
color: red !important;
}
}
Here the paragraph comes out green, even though tema was declared afterwards. With !important the earlier layer wins. It is a logical consequence of the cascade, but it is worth keeping in mind so you don't go crazy debugging.
{: .advice }
Do not turn layers into an excuse to fill the project with !important. Their whole point is precisely to avoid them. Reserve !important for very rare cases, such as overriding styles you don't control.
Going back to the previous layer
There is the value revert-layer, which discards what the current layer has set for a property and lets the value from the previous layer win:
@layer base {
a {
color: blue;
}
}
@layer tema {
a {
color: revert-layer; /* Goes back to the blue from "base" */
}
}
A practical case
A very common architecture splits the whole project into a few layers ordered from least to most priority:
/* 1. We set the order only once */
@layer reset, base, componentes, utilidades;
/* 2. We fill each layer */
@layer reset {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
@layer base {
body {
font-family: sans-serif;
line-height: 1.5;
}
}
@layer componentes {
.boton {
padding: .5rem 1rem;
border-radius: .5rem;
background: royalblue;
color: white;
}
}
@layer utilidades {
.oculto {
display: none;
}
}
With this structure it no longer matters in what order you write the rules or how specific the selectors are. A utility like .oculto will always beat a component, and a component will always beat the base styles. CSS becomes predictable, which is exactly what we want.
Activity 1
Create two layers, base and tema, declaring their order on the first line. In base paint the links black using an id selector. In tema paint them red using a class. Verify that the red ones win even though the id is more specific.
Activity 2
Download a normalizer such as Normalize.css and import it inside a reset layer with @import ... layer(reset). Then add a componentes layer and verify that your styles override those of the normalizer without needing !important.
This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.
Desafíos de programación atemporales y multiparadigmáticos
Te encuentras ante un librillo de actividades, divididas en 2 niveles de dificultad. Te enfrentarás a los casos más comunes que te puedes encontrar en pruebas técnicas o aprender conceptos elementales de programación.
Buy the bookWill you buy me a coffee?
This is how I keep writing without ads or paywalls.
Sure, it's on me!
Comments
There are no comments yet.