Direct link to this sectionIntroduction
Following the SASS documentation there are some hints related when and at which point in a file an @extended
selector is inserted:
Extends are resolved after the rest of your stylesheet is compiled. In particular, it happens after parent selectors are resolved.
Because @extend updates style rules that contain the extended selector, their styles have precedence in the cascade based on where the extended selector’s style rules appear, not based on where the @extend appears.
Taking the following SASS/SCSS example code:
.first {
color: green;
}
.second {
@extend %example-placeholder;
background-color: blue;
}
.third {
@extend %example-placeholder;
background-color: yellow;
}
%example-placeholder {
color: red;
}
Compiles to CSS output:
.first {
color: green;
}
.second {
background-color: blue;
}
.third {
background-color: yellow;
}
/* @extend rule is inserted at %placeholder position */
.third, .second {
color: red;
}
In this example we can influence where in output.css
the @extend rules are inserted. We could move the placeholder above the .second
class so that the rules appear in that place:
.first {
color: green;
}
%example-placeholder {
color: red;
}
.second {
@extend %example-placeholder;
background-color: blue;
}
.third {
@extend %example-placeholder;
background-color: yellow;
}
Compiles to CSS output:
.first {
color: green;
}
/* @extend rule is inserted at %placeholder position */
.third, .second {
color: red;
}
.second {
background-color: blue;
}
.third {
background-color: yellow;
}
In this case it is not very usefull, but there may be situations where the order is important.
Direct link to this sectionHow to not @use
%placeholders
in modules
When I first tried to integrate a %placeholder
in my SASS/SCSS library I encountered a problem. Since my library uses the new @use/@forward
syntax instead of @import
, all contents are divided into files/modules and many @mixins
are used.
I first created a separate file for the %placeholder
, which I then loaded into the component modules like element/_badge.scss
or element/_button.scss
.
Sorry that this example may be a bit complex. I have tried to keep it as short as possible.
%generic-placeholder {
color: red;
}
@use '../placeholder' as *;
@mixin generate-badge {
.bdg {
@extend %generic-placeholder;
background-color: blue;
}
}
@use '../placeholder' as *;
@mixin generate-button {
.btn {
@extend %generic-placeholder;
background-color: yellow;
}
}
This mixin generates all modules.
@use '../element' as *;
@mixin generate-modules {
@font-face {
/* must come first */
}
:root {
/* must come first */
}
@include generate-badge;
@include generate-button;
}
/* forward more stuff first */
@forward 'mixin';
@forward 'mixin/module';
@forward 'element';
The use of my library:
@use 'shrtcss' as *;
@include generate-modules;
Compiled to CSS output:
/* @extend rule is inserted at top of file */
.bdg, .button {
color: red;
}
@font-face {
/* but I want to load fonts first */
}
:root {
/* but I want to load root first */
}
.bdg {
background-color: blue;
}
.button {
background-color: yellow;
}
I had no control over where in the stylesheet the @extend
rules are inserted. This was confusing. Reading the SASS/SCSS docs stating:
Heads up! ... This can be confusing. ...
Okay. So this was confusing and I had to figure out why.
Direct link to this sectionHow to @use
%placeholders
in modules
To regain control we move the %placeholder
inside the @mixin generate-modules
.
@use '../element' as *;
@mixin generate-modules {
@font-face {
/* Must come first */
}
:root {
/* Must come first */
}
%generic-placeholder {
color: red;
}
@include generate-badge;
@include generate-button;
}
We remove the @use
rule from both element files:
@mixin generate-badge {
.bdg {
@extend %generic-placeholder;
background-color: blue;
}
}
@mixin generate-button {
.btn {
@extend %generic-placeholder;
background-color: yellow;
}
}
And here comes the magic part, compiles to CSS output:
@font-face {
/* load fonts first */
}
:root {
/* load root first */
}
/* @extend rule is inserted at %placeholder position! */
.bdg, .button {
color: red;
}
.bdg {
background-color: blue;
}
.button {
background-color: yellow;
}
Direct link to this sectionThis is still confusing 🤯
Why is no compile error thrown? Is %generic-placeholder
known to elements
for instance?
Let's look at the explanation of the documentation again:
Extends are resolved after the rest of your stylesheet is compiled. In particular, it happens after parent selectors are resolved.
Because @extend updates style rules that contain the extended selector, their styles have precedence in the cascade based on where the extended selector’s style rules appear, not based on where the @extend appears.
The important part here I did not get first:
Extends are resolved after the rest of your stylesheet is compiled.
It looks like %generic-placeholder
are known to elements
without loading them explicit. This is possibly because the process takes place after the stylesheet is created, even if %placeholders
are declared in @mixins
.
I find it even more perplexing that there are tons of tutorials regarding the use of @extend
vs @mixin
and %placeholder
, yet none that I have found even begin to address this magic process, nor is it mentioned in the SASS/SCSS documentation.
...styles have precedence in the cascade based on where the extended selector’s style rules appear...
Yes, this now applies.
Direct link to this sectionConfusion Conclusion
Thanks to oscarotero for checking into my code, I hope the confusion is now complete, because in this blog post we learned how to not @use
%placeholders
in complex SASS/SCSS modules.