When extending a selector with the @extend
directive, Sass doesn’t take the CSS content from the extended selector to put it in the extending one. It works the other way around. It takes the extending selector and append it to the extended one.
Because of how it works, it makes it impossible to use it from different scopes. For instance, you can’t extend a placeholder that has been declared in a @media
block, nor can you extend a placeholder from root if you’re within a @media
directive.
Surely we can find a way to use @extend
when possible, mixin otherwise. Indeed, it’s doable but it’s a bit tricky, I call this the mixtend hack. You might want to think twice before implementing everywhere in your project. Perhaps using mixins only would be easier. I’ll leave you the judge of that.
@extend
Wrapping The idea is actually quite simple to grasp. First we define the mixin. The only parameter is $extend
, which defines whether or not the mixin should try extending rather than including. Obviously, it is a boolean (default to true
).
If $extend
is true
, we extend a placeholder named after the mixin (unfortunately, this is not automagically computed). If it’s false
, we dump the CSS code as a regular mixin would do.
Out of the mixin, we define the aforementioned placeholder. To avoid repeating the CSS code in the placeholder, we only have to include the mixin by setting $extend
to false
so it dumps the CSS code in the placeholder’s core.
/// *Mixtend* hack
/// @author Kitty Giraudel
@mixin mixtend-boilerplate($extend: true) {
@if $extend {
@extend %mixtend-boilerplate-placeholder;
} @else {
// Mixtend content
}
}
%mixtend-boilerplate-placeholder {
@include mixtend-boilerplate($extend: false);
}
Example
As a simple example, we will use the micro-clearfix from Nicolas Gallagher.
@mixin clearfix($extend: true) {
@if $extend {
@extend %clearfix;
} @else {
&:after {
content: '';
display: table;
clear: both;
}
}
}
%clearfix {
@include clearfix($extend: false);
}
Using it is quite straightforward:
.a { @include clearfix; }
.b { @include clearfix; }
@media (min-width: 48em) {
.c {
@include clearfix(false);
}
}
Result CSS:
.a:after, .b:after {
content: '';
display: table;
clear: both;
}
@media (min-width: 48em) {
.c:after {
content: '';
display: table;
clear: both;
}
}
Sublime Text snippet
If you want to save the boilerplate in order to make it highly reusable, you will be pleased to know that it is very easy to create a Sublime Text snippet for this. In Sublime, head to Tools > New snippet… and paste the content below.
Feel free to change the key to put whatever floats your boat; it is the word to type before hitting
tab
to expand the snippet. I went with mixtend
.
<snippet>
<content><![CDATA[
@mixin ${1:mixtend}(\$extend: true) {
@if $extend {
@extend %${1:mixtend};
} @else {
${2}
}
}
%${1:mixtend} {
@include ${1:mixtend}(\$extend: false);
}
]]></content>
<tabTrigger>mixtend</tabTrigger>
<scope>source.scss</scope>
</snippet>
This is awesome, it should be the default behavior.
For future reference, when this GitHub issue is solved, this mixtend should no longer be necessary:
https://github.com/sass/sass/issues/1050
I don’t understand it. Why all the “false” setup when it outputs exactly the same?