This content originally appeared on Bram.us and was authored by Bramus!
When writing CSS, we developers have to carefully think about how we write and structure our code. Without any proper “plan of attack” the Cascade can suddenly work against us, and we might end up with pieces of code overwriting each other, selectors getting heavier and heavier, a few !important
modifiers here and there, … — Uhoh!
Thanks to an upcoming feature named Cascade Layers (CSS @layer
) we’ll be able to deal with all this more easily. Let’s take a look at what these Cascade Layers are, and how we can use them to our benefit …
~
Table of Contents
- The CSS Cascade, a Quick Primer
- Taming the Cascade
- Introducing Cascade Layers
- Intermediate Summary
- Details you need to know
- A few more notes / caveats
- Browser Support
- In Closing
~
# The CSS Cascade, a Quick Primer
The CSS Cascade is the algorithm which CSS uses to resolve competing declarations that want to be applied to an element.
/* HTML: <input type="password" id="password" style="color: blue;" /> */
input { color: grey; }
input[type="password"] { color: hotpink !important; }
#password { color: lime; }
Will the input
color be grey
, lime
, hotpink
, or blue
? Or the User-Agent default black
?
To determine which declaration should “win” (and thus be applied), the Cascade looks at a few criteria. Without taking Cascade Layers into account just yet, these criteria are:
- Origin and Importance
- Context
- Style Attribute
- Specificity
- Order of Appearance (aka Source Code Order)
These criteria are ranked from high to low priority, and are checked one after the other until a winning declaration has been determined. In case it is undecided which property declaration will “win” at a higher criterium, the Cascade will move on to the next criterium.
For more in-depth info, please refer to this very good post by Amelia Wattenberger on the subject.
? And oh, when it comes to Specificity I’m still very fond of the Specificity Wars post that first taught me about it.
~
# Taming the Cascade
When authoring CSS we place our CSS mainly into one and the same origin: the Author Origin. As a result, we end up juggling with Selector Specificity and Order of Appearance as our ways to control the cascade. While doing so, we have to perform a fine balancing act between both of these aspects:
- Statements that use selectors of a high specificity can cause problems in case you want to override some properties later in the code. This often leads to even more heavy selectors or the use of
!important
, which in itself can raise even more issues. - Statements that use selectors of a low specificity can be overwritten too easily by statements that appear later in the code. This can especially be troublesome when loading third-party CSS after your own code.
To help us tame those aspects of the Cascade, a few clever developers have come up with methodologies such as BEM, ITCSS, OOCSS, etc. over time. These methodologies mainly lean on the following aspects:
- Structuring your code in such a way that you create some sort of logical order that works for most scenarios.
- Keeping Selector Specificity as low as possible by leaning primarily to classes.
The almighty Inverted Triangle of CSS.
While these approaches can certainly help you strike a balance between Selector Specificity and Order of Appearance, they are not 100% closing:
- The established order is never really enforced as Order of Appearance still determines things.
- Selector Specificity still has the upper hand over the order of the layers
~
# Introducing Cascade Layers
To make this balancing act more easy, there’s a new mechanism named Cascade Layers being worked on. It’s a feature that was proposed to the CSSWG at the end of 2019 by Miriam Suzanne. By now, Cascade Layers have been approved by the WG, and are part of the upcoming CSS Cascading and Inheritance Level 5 (css-cascade-5) Specification.
With Cascade Layers you can split your CSS into several layers via the @layer
at-rule. Because of its unique position in the Cascade, using Layers comes with a few benefits that give us developers more control over the Cascade.
The new CSS Cascade with Layers added to it
As per spec:
In the same way that Origins provide a balance of power between user and author styles, Cascade Layers provide a structured way to organize and balance concerns within a single Origin.
Let’s dive in with some code examples, explaining the benefits along the way.
? Before we continue: try and forget any assumption you have about “layers in CSS”. By simply looking at their name, it’s easy to confuse Cascade Layers with layering via z-index
or the (deprecated) HTML <layer>
element. These things are not the same:
- Layering via
z-index
is about visually stacking boxes onto a webpage. - The HTML
<layer>
element is ancient history. - Cascade Layers is about structuring your CSS Code and controlling the CSS Cascade.
~
# Creating a Cascade Layer
A Cascade Layer can be declared in several ways:
-
Using the
@layer
block at-rule, with styles assigned immediately to it:@layer reset { * { /* Poor Man's Reset */ margin: 0; padding: 0; } }
-
Using the
@layer
statement at-rule, without any styles assigned:@layer reset;
-
Using @import with the
layer
keyword orlayer()
function:@import(reset.css) layer(reset);
Each of these standalone examples, creates a Cascade Layer named reset
.
? A possible 4th way is still being worked on: by means of an attribute on a <link>
element. See CSSWG Issue #5853.
~
# Managing Layer Order
Cascade layers are sorted by the order in which they first are declared.
In the example below we create four layers: reset
, base
, theme
, and utilities
.
@layer reset { /* Create 1st layer named “reset” */
* {
margin: 0;
padding: 0;
}
}
@layer base { /* Create 2nd layer named “base” */
…
}
@layer theme { /* Create 3rd layer named “theme” */
…
}
@layer utilities { /* Create 4th layer named “utilities” */
…
}
Following their declaration order, the Layer Order becomes:
reset
base
theme
utilities
Cascade layers are sorted by the order in which they first are declared.
When re-using the name of a Layer, styles will be appended to the already existing Layer. The order of the Layers remains the same, as it’s only the first appearance which determines the order:
@layer reset { /* Create 1st layer named “reset” */
…
}
@layer base { /* Create 2nd layer named “base” */
…
}
@layer theme { /* Create 3rd layer named “theme” */
…
}
@layer utilities { /* Create 4th layer named “utilities” */
…
}
@layer base { /* Append to 2nd layer named “base” */
…
}
The fact that the Layer order remains the same when re-using a Layer name makes the @layer
statement at-rule syntax darn handy. Using it, you can establish Layer Order upfront, and append all CSS later to it:
@layer reset; /* Create 1st layer named “reset” */
@layer base; /* Create 2nd layer named “base” */
@layer theme; /* Create 3rd layer named “theme” */
@layer utilities; /* Create 4th layer named “reset” */
@layer reset { /* Append to layer named “reset” */
…
}
@layer theme { /* Append to layer named “theme” */
…
}
@layer base { /* Append to layer named “base” */
…
}
@layer theme { /* Append to layer named “theme” */
…
}
Heck, you can write it even shorter, using a comma-separated list of Layer Names:
@layer reset, base, theme, utilities;
? Best Practice: To keep control over Layer Order, it’s recommended to declare all your layers upfront by using this one-line syntax, and —once the order is established— then append styles to them.
~
# Cascade Layers and the Cascade
In the Cascade (the algorithm), Layers get a higher precedence than Specificity and Order of Appearance. So the criteria of the Cascade become this (in order):
- Origin and Importance
- Context
- Style Attribute
- Layers
- Specificity
- Order of Appearance
The new CSS Cascade with Layers added to it
When evaluating the Layers criterium, the Cascade will look at the Layer Order to determine the winning declaration. Declarations whose whose cascade layer is last, will win from declarations in earlier-declared Layers (cfr. how Order of Appearance works: last one wins).
Cascade layers (like declarations) are ordered by order of appearance. When comparing declarations that belong to different layers, then for normal rules the declaration whose cascade layer is last wins […]
How the Cascade evaluates Layers
Take this snippet from earlier:
@layer reset, base, theme, utilities;
In total we create 4 layers, in this order:
reset
base
theme
utilities
For example: Competing declarations in the theme
Layer (3) will win from declarations in the base
(2) and reset
(1) Layers because those Layers were declared before theme
. Competing declarations in the theme
Layer (3) however won’t win from those in utilities
(4), as that Layer has been declared later.
Once a winning declaration has been determined via Layer Order, the Cascade won’t even look at Specificity or Order of Appearance anymore. This is because Layers is a separate and higher ranked criterium of the Cascade.
Practical example:
@import(reset.css) layer(reset); /* 1st layer */
@layer base { /* 2nd layer */
form input {
font-size: inherit;
}
}
@layer theme { /* 3rd layer */
input {
font-size: 2rem;
}
}
Although the input
-selector (Specificity 0,0,1
) used on line #10 is less specific than the form input
-selector (Specificity 0,0,2
) from line #4, the declaration on line #10 will win because the theme
Layer (3) is ordered after the base
layer (2).
? Because later-declared Layers always win from earlier-declared Layers, you —as a developer— don’t need to worry about the Specificity nor Order of Appearance that is used in those other Layers: it’s the Layer Order that dictates who the winner in case of conflict is.
This also means that you can easily move Layers around, knowing that their Layer Order —and not the Specificity nor Order of Appearance— will determine things.
‼️ Do note that this doesn’t mean that Specificity and Order of Appearance are no longer important. These two criteria still are, but only inside one and the same Layer. When comparing declarations between Layers, these two criteria can be ignored.
~
# Intermediate Summary
If you were able to follow along there, this intermediate summary should make sense:
- With Cascade Layers you can split your CSS into several layers.
- Upon creating a Layer with
@layer
, you also determine the Layer Order. - Re-using Layer names will append to the already created Layer, without altering Layer Order.
- When evaluating Layers, the Cascade (the algorithm) will have declarations placed in later-declared Layers win from declarations in early-declared Layers.
- The Cascade evaluates Layers before Specificity and Order Of Appearance. That way you no longer need to worry about these two criteria for CSS found in separate Layers, as Layer Order will already have determined the winning declaration.
Cool, right?! ?
~
# Details you need to know
There’s a few details that one needs to know about the inner workings of Cascade Layers.
# Unlayered Styles come first in the Layer Order
Styles that are not defined in a Cascade Layer will be collected in an implicit layer. This implicit layer will be positioned first in the Layer Order.
Unlayered Styles come first in the Layer Order
Because of this position, styles declared in Layers will always override Unlayered Styles.
@import(reset.css) layer(reset); /* 1st layer */
@layer base { /* 2nd layer */
h1 { font-family: }
}
@layer theme { /* 3rd layer */
h1 { color: rebeccapurple; }
}
@layer utilities { /* 4th layer */
[hidden] { display: none; }
}
/* Unlayered Styles become part of an implicit first layer */
body h1 { color: black; }
The Layer Order for this snippet looks like this:
- (unlayered styles)
reset
base
theme
utilities
The result from the example above will be that the h1
will be colored rebeccapurple
, even though the unlayered styles have a higher Specificity, and come later in the source order.
? In the future we might gain the ability to control the layer position of these unlayered declarations. This is being tracked in CSSWG Issue #6323
~
# Naming a Layer is optional
Cascade Layers can also be created without giving them a name. These are called “Anonymous Layers”.
-
Using the
@layer
block at-rule, with styles assigned immediately to it@layer { * { /* Poor Man's Reset */ margin: 0; padding: 0; } }
-
Using
@import
:@import(reset.css) layer;
A disadvantage of not using a name is that you can’t append to these anonymous layers:
@layer { /* layer 1 */ }
@layer { /* layer 2 */ }
@import url(base-forms.css) layer; /* layer 1 */
@import url(base-links.css) layer; /* layer 2 */
? Using the @layer
statement at-rule without a name (e.g. @layer;
) is possible, but not mentioned as it’s a useless statement to make:
- It has no content to begin with
- You can’t append extra content since you can’t refer to it.
~
# Layers can be nested
It’s perfectly fine to nest @layer
statements.
@layer base { /* 1st Layer */
p { max-width: 70ch; }
}
@layer framework { /* 2nd Layer */
@layer base { /* 1st Child Layer inside 2nd Layer */
p { margin-block: 0.75em; }
}
@layer theme { /* 2nd Child Layer inside 2nd Layer */
p { color: #222; }
}
}
In this example there’s two outer layers:
base
framework
The framework
layer itself also contains two layers:
base
theme
? The re-use of the name base
does not conflict here, as that 2nd base
is part of the framework
layer. Yes, the names are scoped to their surrounding outer-layer (if any)
Representing the Layers as one combined tree, it would look like this:
base
-
framework
base
theme
To refer to a Layer that is contained inside an other Layer, use it’s full name which uses the period to determine the hierarchy, e.g. framework.theme
.
The flattened Layer Tree for this code example would then look like this:
base
framework.base
framework.theme
To append styles to a nested Layer, you need to refer to it using this full name:
@layer framework {
@layer default {
p { margin-block: 0.75em; }
}
@layer theme {
p { color: #222; }
}
}
@layer framework.theme {
/* These styles will be added to the theme layer inside the framework layer */
blockquote { color: rebeccapurple; }
}
~
# A few more notes / caveats
If you still haven’t had enough, there are a few extra things worth mentioning.
?? Already had enough? Feel free to skip this part and immediately jump to Browser Support as it becomes pretty advanced/complicated.
# Cascade Layers and the use of !important
When evaluating the Origin criterium, the Cascade orders the several Origins as follows (ranked from high to low):
- Transitions
- Important User-Agent
- Important User
- Important Author
- Animations
- Normal Author
- Normal User
- Normal User-Agent
Notice how Origins with !important
have the reverse order of their normal (i.e. non-important) counterpart? That’s because of how CSS works:
When a declaration is marked !important, its weight in the cascade increases and inverts the order of precedence.
This inversion-rule is also applied declarations in Cascade Layers: declarations with !important
annotation will be put in the “Important Author” Origin, but the Layers will have the inverse order when compared to the “Normal Author” Origin.
Cascade Layers vs. use of !important
Winging back to our four layers from earlier:
@layer reset, base, theme, utilities;
Normal declarations in these layers all go in the “Normal Author” Origin, and will be ordered as such:
- Normal
reset
Layer - Normal
base
Layer - Normal
theme
Layer - Normal
utilities
Layer
Important declarations in these layers however all will go in the “Important User” Origin, and will be ordered in reverse:
- Important
utilities
Layer - Important
theme
Layer - Important
base
Layer - Important
reset
Layer
Because “Normal Unlayered Styles” implicitly go first, this also means that “Important Unlayered Styles” will go last then.
? So yes, an !important
declaration inside your Unlayered Styles will win from an !important
declaration inside a Layer.
~
# Cascade Layers vs. Media Queries (and other conditionals)
When a @layer
is nested inside a Media Query (or any other conditional), and the condition does not evaluate to true
, the @layer
will be not be taken into account for the Layer Order. Should the Media Query/Conditional evaluate to true
later on — because of the screen size changing for example — Layer Order will be recalculated.
For Example:
@media (min-width: 30em) {
@layer layout {
.title { font-size: x-large; }
}
}
@media (prefers-color-scheme: dark) {
@layer theme {
.title { color: white; }
}
}
If the first Media Query matches (based on viewport dimensions), then the layout
layer will come first in the Layer Order. If only the color-scheme
Preference Query matches, then theme
will be the first layer.
Should both match, then the Layer Order will be layout
, theme
. If none matches no Layers are defined.
~
# Cascade Layers vs. “Name-Defining Rules”
Name-Defining Rules — such as @keyframes
, @scroll-timeline
, @font-face
— follow Layer Order as you’d expect:
@layer framework, override; /* Establish Layer Order */
@layer framework {
@keyframes slide-left {
from { margin-left: 0; }
to { margin-left: -100%; }
}
}
@layer override {
@keyframes slide-left {
from { translate: 0; }
to { translate: -100% 0; }
}
}
.sidebar { animation: slide-left 300ms; }
The Layer Order looks like this:
framework
override
As the last layer wins, the slide-left
Keyframes from the override
Layer (2) — the ones using translate
— will be used.
~
# No Interleaving of @import
/@namespace
and @layer
For parsing reasons (see CSSWG Issue #6522) it’s not allowed to interleave @layer
with @import
/@namespace
rules.
From the moment the CSS parser sees a @layer
that follows an earlier @import
, all subsequent @import
rules after it will be ignored:
@layer default;
@import url(theme.css) layer(theme);
@layer components; /* ? This @layer statement here which comes after the @import above … */
@import url(default.css) layer(default); /* ❗️ … will make this @import rule (and any other that follow) be ignored. */
@layer default {
audio[controls] {
display: block;
}
}
To counteract this, group your @import
rules together.
@layer default;
@import url(theme.css) layer(theme);
@import url(default.css) layer(default);
@layer components;
@layer default {
audio[controls] {
display: block;
}
}
? Best Practice: Should you rely on @import
(which you shouldn’t, as it’s a performance hit) best is to:
- Establish a layer order upfront using
@layer
statement at-rules - Group your
@import
s after that - Append styles to already established layers using
@layer
block at-rules
@layer default, theme, components;
@import url(theme.css) layer(theme);
@import url(default.css) layer(default);
@layer default {
audio[controls] {
display: block;
}
}
~
# Browser Support
I’m very happy to see that all browser vendors have already started work on supporting Cascade Layers ?. It’s all still early works / experimental, so they’re not shipping to production yet.
- Chromium (Blink)
Available in Chrome 96+ (current Canary) with the
--enable-blink-features=CSSCascadeLayers
run-time flag.As per design doc, aimed target release is M98 (late 2020) or M99 (early 2021)
- Firefox (Gecko)
Available in Firefox 94+ (current Canary) by setting
layout.css.cascade-layers.enabled
totrue
viaabout:config
- Safari (WebKit)
This commit introduces a
CSSCascadeLayersEnabled
Preference, but doesn’t look like you can toggle it yourself.
The demo below — by Miriam — will show a green checkmark when @layer
support is enabled.
See the Pen OMG, Layers by Miriam Suzanne (@miriamsuzanne) on CodePen.
To stay up-to-date regarding browser support, you can follow these tracking issues:
- Chromium: Issue #1095765
- Firefox: Issue #1699215
- Safari: Issue #220779
~
# In Closing
With Cascade Layers coming, we developers will have more tools available to control the Cascade. The true power of Cascade Layers comes from its unique position in the Cascade: before Selector Specificity and Order Of Appearance. Because of that we don’t need to worry about the Selector Specificity of the CSS that goes into each layer, nor about the order in which we load CSS into these layers — something that will come in very handy for larger teams or when loading in third-party CSS.
Personally I’m really looking forward to give Cascade Layers a try. Being able to enforce the ordering used in ITCSS at the language level for example, feels like a great win.
~
To help spread the contents of this post, feel free to retweet the announcement tweet:
The Future of CSS: Cascade Layers (CSS @layer)
— Bram.us (@bramusblog) September 15, 2021
? https://t.co/hSC8HCwhi2
? #cascade #css #layers #specificity pic.twitter.com/P0bf4nY8e1
~
? Like what you see? Want to stay in the loop? Here's how:
? Thanks to Miriam, Hidde, Sam, Tim, Stefan, Nils, and Adam for their valuable feedback on early versions of this post.
This content originally appeared on Bram.us and was authored by Bramus!
Bramus! | Sciencx (2021-09-15T12:09:30+00:00) The Future of CSS: Cascade Layers (CSS @layer). Retrieved from https://www.scien.cx/2021/09/15/the-future-of-css-cascade-layers-css-layer/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.