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!
To regain control over the Cascade in those situations there’s a new CSS Language Feature coming to help us: Cascade Layers (CSS @layer
).
Let’s take a look at what they are, how we can use them, and what benefits they bring …
~
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 criterion, the Cascade will move on to the next criterion.
For more in-depth info, please refer to my CSS Day 2022 talk on the subject. It also covers Cascade Layers, so you can watch the entire thing instead of continuing to read 😉
You can find the the slides here.
~
# 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 — if not having thrown in an !important
somewhere. 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 CSS feature led by Miriam Suzanne — whom you might also know from CSS Container Queries — and is 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. 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.
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
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 url(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 existing 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 “utilities” */
@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 criterion, the Cascade will look at the Layer Order to determine the winning declaration. Declarations 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 check Specificity or Order of Appearance for those declarations anymore. This is because Layers is a separate and higher ranked criterion of the Cascade.
Practical example:
@import url(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 (i.e. “Last Layer Wins”).
- 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?! 🤩
~
💁♂️ Like what you see so far? Happen to be conference or meetup organiser? Feel free to contact me to come speak at your event, with a talk covering the contents of this post.
~
# 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 last in the Layer Order
Update 2021.10.07: The CSS Working Group has decided that Unlayered Styles should come last (instead of first as it was specced before). This post has been updated accordingly.
Styles that are not defined in a Cascade Layer will be collected in an implicit layer. This implicit layer will be positioned first last in the Layer Order.
Unlayered Styles come last in the Layer Order
Because of this position, Unlayered Styles will always override styles declared in Layers.
@import url(reset.css) layer(reset); /* 1st layer */
/* Some Unlayered Styles */
h1 { color: hotpink; }
@layer base { /* 2nd layer */
h1 { font-family: … }
}
@layer theme { /* 3rd layer */
body h1 { color: rebeccapurple; }
}
@layer utilities { /* 4th layer */
[hidden] { display: none; }
}
The Layer Order for this snippet looks like this:
reset
base
theme
utilities
- (unlayered styles)
The result from the example above will be that the h1
will be colored hotpink
, even though the unlayered styles come earlier the Source Order and the used selector in theme
Layer has a higher Specificity.
💡 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 url(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 criterion, 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 Author” 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 last, this also means that “Important Unlayered Styles” will go first then.
💡 So yes, an !important
declaration inside a layer will win from an !important
declaration inside Unlayered Styles.
~
# 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 are working on adding have support for Cascade Layers 🥳. It’s all still experimental support, but given that the spec has matured since it got first proposed in 2019, things are looking very good to see it shipped by the end of this year / early next year.
- Chromium (Blink)
✅ Supported in Chromium 99 and up
Was supported as an Experimental Feature in Chrome 96+ through the
--enable-blink-features=CSSCascadeLayers
run-time flag. Starting with version 96.0.4661.0, you could toggle it via the#enable-cascade-layers
feature flag inchrome://flags/
- Firefox (Gecko)
✅ Supported in Firefox 97 and up
Was supported as an Experimental Feature in Firefox 94+ (current Canary) by setting
layout.css.cascade-layers.enabled
totrue
viaabout:config
.- Safari (WebKit)
✅ Supported in Safari 15.4 and up
Was supported as an Experimental Feature in Safari Technology Preview 133. Had to be enabled manually by using Safari’s App Menu in the Menu Bar and choose Develop → Experimental Features → CSS Cascade Layers.
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 is used in other Layers, 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:
They should have just called this cascade order ( maybe C Order? or c-order as a way to make it fit the z-order naming convention? ) instead – the layers naming is confusion for no reason.
Seems like a whole lot of complication for nothing
True! If our average framework-loving developer can’t maintain nice and tidy CSS now, then these layers will destroy him 😀
Completely agree!
Reminds me of a joke:
– We have 24 concurrent standards describing same thing, any ideas how deal with that?
– Yes! Let’s make a new standard which will replace all previous ones and solve their problems!
…after a while: now we have 25 concurrent standards
Using Layers is entirely optional. If you’ve never had Specificity clashes and have total control over your Source Code Order, then you don’t need to use them 😉
I’m excited about this one. I just joined a team with lots of selectors like #sidebar-menu > ul > li > a and it’s too much work to refactor it all.
Agreed. Sounds like a waste of time.
Probably from the same creators of “!important”
I am very excited to see this feature! However I would be curious on how to solve one common problem that led to the abomination of JSS:
can and should we use layers for every component?
So let’s say you have an application with global styles and components like main panels, main layout elements defined and you have several teams dropping new features onto this.
People got angry with CSS often because someone fixing one bug in their codebase moved something else in an other team member’s component. So people started hacking the system with hiding from their precious component related code from the browser (meaning also the browser could not do any pre-runtime CSS optimizations as well – but hey at least that dev from Tiger Team cannot break your code!).
So if we can use a new language element to have this layer order:
1. company global theming
2. application specific layout
3. component specific layout
we are going to make the biggest complaint about CSS obsolete.
Note, that by `component` specific layout I meant several layers named after the component. Having just one giant `component` layer would mean that that dev who learnt CSS when PHP was all the rage back in 2008 can still break my components code.
This also means we can have hundreds of one-file scoped layers without issues.
Interesting idea, which could work.
Note that we’re also about to get Scoping in CSS, that can solve that issue.
I love seeing that it’s in the works! In a way, I see myself utilizing this in my codebase, but somehow I wonder if the benefits would be felt. Meaning — what are the real use cases that this would improve?
If you have a code structure that takes such naming conventions in mind, do you really need the layers? But since there are very few developers that can do a perfect code structure (me included for sure), then using layers would be beneficial. But! The bad practices such developers apply would just carry over to layers, no? So my wondering is — what is the real improvement over the codebase layers would provide to mid-level developers?
Just wondering of opinions here, I don’t have a strong one yet ^^
> What are the real use cases that this would improve
Big projects and teams, with external styles.
> If you have a code structure that takes such naming conventions in mind, do you really need the layers?
Yes, see last paragraph + bullet list in https://www.bram.us/2021/09/15/the-future-of-css-cascade-layers-css-at-layer/#taming-the-cascade
I noticed you put theme twice in this example – which I think you meant to put utilities.
https://i.imgur.com/kzQYjR9.png
Yes, that’s correct. The four layers are created, but there’s content being appended to the
theme
layer twice. Theutilities
layer remains empty in this example.Can you declare levels using the at-rule without styles, or only with the block at-rule.
In other words, can you do this?
@layer base, framework.base, framework.theme
Yes, that’s perfectly possible as the
@layer
statement accepts any<layer-name>
, which isThat’s a very interesting feature. Unfortunately, W3C’s strange decision to overwrite all layers with non-layered styles is counter-intuitive and severely limits the usefulness of layers – if you don’t go all-in with layers most of your styles will never be seen.
Do you know why W3C wanted it this way?
Initially, Layer Order was defined as it is now: Unlayered Styles go last and override Layered Styles.
It was reverted at a certain point in time, but that wasn’t received well. For example:
After that, the decision to revert got reverted, thus making Unlayered Styles win from Layered Styles again.
This way, we — as authors — can load external styles into Layers, while keeping our own CSS unlayered. Thanks to this, we don’t need to worry about the specificity of our own styles vs. that of the external libs: our styles will always win from the layered styles.
As an author from the AMP library replied:
The code example given along with that, is this:
In essence: we, authors, can mainly keep on writing unlayered styles. Should our own stylesheets become rather complex/big, we can choose to layer up our own styles in addition to those other (third-party / library / reset) layers.
I would be easy to create a layer after importing the framework styles. Something like “@layer overrrides”.
I think the decision to give unlayered styles higher precedence hurts the usefulness of @layer. As a consequence, your carefully crafted layers will always be destroyed by devs who just forgot to respect the layers. What is the point of creating a @utilities layer, for example, if you can’t make the power of those utility classes bigger than the power of unlayered styles? If you CSS reset all the stuff, how will you ever be able to override that with CSS layers?
Would this support something like:
?
Currently CSS’
@import
has to exist at the top of the file beneath@charset
. I think it would be really understandable and clean to make an exception when inside a@layer
.That’s not allowed. What you can do instead is:
@layer
statement to predefine your layering order@import
external stylesheets into one of those layersLike so:
Ah, that’s unfortunate. It’s not the end of the world, but it’s a lot easier to see the layering if it allowed nesting but my nesting example probably opens a whole can of worms that I can’t even begin to imagine.
Great article!
Just a small update for the article: Safari 15.4 on macOS (Monterey, Big Sur, Catalina) and iOS shipped with CSS Cascade Layers enabled by default: https://webkit.org/blog/12445/new-webkit-features-in-safari-15-4/#css
Hi Al! Updating this post is still on my TODO. Thanks for reminding me 🙂
No worries! Keep up the good work.
After resetting the margin and padding in universal selector inside implicit layer when I try to give some margin and padding to any element inside a explicit layer (`@layer`) then It does not work, can you explain why??
After wrapping universal selector in another explicit layer, It is working.
“`
* {
margin: 0;
padding: 0;
}
@layer card-component {
.card {
padding: 1em; // It is not applied to card
}
}
“`
As detailed in this post, unlayered styles have the ultimate priority and win from layered styles.
To fix, you should put your reset in a separate layer, preferably the one with the lowest priority.
How does @layer work in css modules?
Maybe @layer will be usable at some point in the future, but until ALL stylesheets and styles EVERYWHERE ON THE INTERNET are defined in a layer, this feature is, by default, unusable.
The reason is the very simple fact that anything outside of a @layer definition takes precedence. So all your normalize stylesheets, or legacy Bootstrap CSS files, etc, all of those, every single one cannot be overridden once you put your styles in a @layer. They all receive a de facto !important flag.
IMO no one should use this feature.
Yeah. As always, politics leads those committees into bad decisions. “Framework authors” (A.K.A corporations with money) won the debate and ruined the spec for everyone. 🙁