Key Takeaways
- Using Sass @extend can lead to unpredictable results, as it’s impossible to anticipate the outcome when extending a selector. The extended selector could be present multiple times, leading to potential issues.
- The argument that @extend is better than mixins for performance due to grouping selectors and reducing duplication isn’t necessarily valid. Once Gzip compression is applied, the difference in file size is likely negligible.
- @extend cannot be used across different media contexts, which can be a significant limitation. Using mixins instead allows for more flexibility and avoids this issue.
- Mixins offer more flexibility and power than @extend. They can accept arguments, define default values, and allow any content to be passed through @content. @extend is more rigid and can require significant refactoring if you need to introduce variables.
About a year ago, I wrote Mixin or Placeholder (my first article here at SitePoint) immediately followed by What Nobody Told You About Sass Extend. And here I am, one year later, changing my mind again and writing why I think the @extend
directive from Sass is really far from being the Eldorado.
TL;DR: Extending is invisible. Extending doesn’t necessarily help file weight, contrary to the saying. Extending doesn’t work across media queries. Extending is not flexible. Mixins have absolutely no drawback.
But first, I should probably give you a little reminder. @extend
is a Sass feature aiming at providing a way for a selector A to extend the styles from a selector B. When doing so, the selector A will be added to selector B so they both share the same declarations. For instance:
.selector-A {
@extend .selector-B;
unicorn: true;
}
.selector-B {
rainbow: true;
}
This Sass snippet will yield this CSS:
.selector-A {
unicorn: true;
}
.selector-B,
.selector-A {
rainbow: true;
}
That is pretty much all you need to know about @extend
to fully appreciate this article so now that we’ve covered the basics, it’s time to move on. There are quite a few reasons why I think @extend
is a deceptive feature.
However worry not, this is not yet another article about how “Sass @extend
can output outrageously long selectors if you’re messing with it”. Let’s try to dig deeper than that, shall we?
Extending is invisible
Sass is just a tool. It is meant to help developers write CSS. However in this case write means so much more than just writing code. It also means maintain. Sass is a tool to help people come back to a stylesheet and update the code without spending hours figuring what’s going on and where else this #FA7A55 color lies.
Because of this, Sass as any other tool, should be transparent. When writing a line of Sass, we should be able to figure out what CSS would result from it. For instance, if I give you this:
// _mixins.scss
@mixin center($max-width) {
max-width: $max-width;
width: 100%;
margin: 0 auto;
}
// main.scss
.container {
@include center(1170px);
}
You can tell me just by looking at the mixin that the resulting CSS will be:
.container {
max-width: 1170px;
width: 100%;
margin: 0 auto;
}
It is very easy to figure this out because you know that including a mixin takes the content of the mixin, replaces the variables with the given arguments and prints it at the very position the mixin has been included. This is actually what’s cool with mixins, they are obvious.
And this is the first problem with @extend
. Whenever you decide to extend a selector, you have absolutely no idea what is going to happen. For what you know, the result could range from doing nothing to causing disastrous side-effects. The extended selector could be present once (best scenario), or multiple times inside compounded selectors which would be terrible.
Now, there are obviously cases where you know that the extended selector is present once and only once in the whole stylesheet, in which case it’s perfectly fine to extend it. That’s the case for helpers for instance. Consider:
%clearfix::after {
content: '';
display: table;
clear: both;
}
This is usually part of your _helpers.scss
file, included very close to the top of your stylesheet. The %clearfix
placeholder won’t be part of any other selector. Ever. Thus, extending it is quite transparent: the extending selector will jump on top of the stylesheet to join %clearfix::after
.
Anyway, this leads me to think that there is a single valid reason to extend a selector: it is a helper that exists only once in the whole stylesheet. And even for this… I think a mixin is still better.
Extending is not necessarily better than mixins regarding performance
There is an old rumor stating that @extend
is better than mixins for performance reasons because they group selectors rather than duplicating the same CSS declarations over and over again. It’s common to say that:
%toolicorn {
unicorn: rainbow;
status: happiness;
&::grandeur {
level: infinite;
}
}
.wanna-be-a-unicorn {
color: hotpink;
@extend %toolicorn;
}
.wanna-be-a-unicorn--too {
color: deepskyblue;
@extend %toolicorn;
}
… is much better for performance than:
@mixin toolicorn {
unicorn: rainbow;
status: happiness;
&::grandeur {
level: infinite;
}
}
.wanna-be-a-unicorn {
color: hotpink;
@include toolicorn;
}
.wanna-be-a-unicorn--too {
color: deepskyblue;
@include toolicorn;
}
On paper, that’s very true. The first CSS output is both shorter and possibly more elegant than the second. See for yourself, this is the @extend
version (285 characters):
.wanna-be-a-unicorn,
.wanna-be-a-unicorn--too {
unicorn: rainbow;
status: happiness;
}
.wanna-be-a-unicorn::grandeur,
.wanna-be-a-unicorn--too::grandeur {
level: infinite;
}
.wanna-be-a-unicorn {
color: hotpink;
}
.wanna-be-a-unicorn--too {
color: deepskyblue;
}
And this is the mixin-powered version with 304 characters:
.wanna-be-a-unicorn {
color: hotpink;
unicorn: rainbow;
status: happiness;
}
.wanna-be-a-unicorn::grandeur {
level: infinite;
}
.wanna-be-a-unicorn--too {
color: deepskyblue;
unicorn: rainbow;
status: happiness;
}
.wanna-be-a-unicorn--too::grandeur {
level: infinite;
}
For such a small example, the difference is quite minimal however it can quickly ramp up with larger content for the mixin/placeholder. So again, on paper it makes sense to think that extending placeholders rather than including mixins is better for file size.
However files are usually served gzipped from the server. Gzip is based on DEFLATE and LZ77; quickly put, the more a string is repeated, the better it is for compression. When putting this in context, it means that the difference is likely to be inexistant once Gzip has rolled over the whole thing.
This is exactly the same debate as for duplicated media queries when putting them inside selectors. It makes no difference once Gzip has done its thing:
… we hashed out whether there were performance implications of combining vs scattering Media Queries and came to the conclusion that the difference, while ugly, is minimal at worst, essentially non-existent at best.
Sam Richards in regards to Breakpoint
So, using @extend
rather than mixins because of code duplication is not a valid point. Moving on.
Extending is not possible across media contexts
You know about that, right? When trying to extend an outer selector from within a @media
directive, Sass chokes and says:
You may not @extend an outer selector from within @media.
You may only @extend selectors within the same directive.
There have been attempts to polyfill this with hack-y solutions (including mine published here on SitePoint), but this is not a good idea. You’d be a fool to use such things in production.
Reasons why we cannot extend across different media contexts are strictly technical at this point. Still, this is something quite annoying to deal with. To work around this issue, the less worse idea is to wrap placeholders with a mixin so you can choose either to @extend
or to include, depending on whether your in a @media
block or not. I described this technique here and this is still what I use at work. Quick proof of concept:
// _helpers.scss
@mixin clearfix($extend: true) {
@if $extend == true {
@extend %clearfix;
} @else {
&::after {
content: '';
display: table;
clear: both;
}
}
}
%clearfix {
@include clearfix($extend: false);
}
// main.scss
.container {
@include clearfix;
}
@media (min-width: 42em) {
.my-other-selector {
@include clearfix($extend: false);
}
}
It works fine and does not involve much complication as far as I know, yet it’s still another layer of abstraction in some way. However, the code might looks a bit complex for someone getting started with Sass.
Anyway, you cannot extend a selector across different media directives, and this plain sucks. For this reason, it might be worth moving to a mixin based setup, where styles might get repeated (see section Extending is not necessarily better than mixins regarding performance) but you can do whatever you want, whenever you want, the way you want.
Extending is not flexible
By their own definition, mixins make a more powerful feature than @extend
. Indeed, they:
- accept arguments;
- can define default values for their arguments;
- can define variable arguments (also you might want to read this article about mixin arguments);
- allow any content to be passed through
@content
.
All those reasons make them the feature of choice if you are heading towards flexibility. It gets very easy to add an extra argument to a mixin because you need it to do more, without introducing any API break whatsoever.
On the other hand, @extend
is quite strict. If you need your code snippet to accept a variable, you have to change the placeholder into a mixin, then update all the rules extending this placeholder to include the mixin instead. Not only quite tedious, but certainly not very flexible.
Final thoughts
So @extend
sucks, is that it? I am not sure. Maybe I am simply not skilled enough to see the real potential of this feature, but it seems I am not the only one to realize @extend
is really not that a killer-feature. Indeed, I suggest you read those articles to push things further:
- When to use @extend; when to use a mixin by Harry Roberts;
- Don’t over-@extend yourself in Sass by Fred Meyer.
Anyway. @extend
can be messy. My advice would be to avoid it when possible and use mixins instead, because at the end of the day, it makes absolutely no difference.
Frequently Asked Questions (FAQs) about Sass Extend
What is the main purpose of using Sass Extend?
Sass Extend is a feature in Sass, a preprocessor scripting language, that allows you to share a set of CSS properties from one selector to another. It helps in keeping your CSS DRY (Don’t Repeat Yourself), which means you can avoid writing repetitive code. It’s a powerful tool that can make your code more efficient and easier to maintain. However, it’s important to use it wisely as it can lead to unexpected results if not used correctly.
Why should I avoid using Sass Extend?
While Sass Extend can be a powerful tool, it can also lead to complications if not used correctly. One of the main issues is that it can produce bloated CSS output, especially when used with nested selectors. This can lead to performance issues as the browser has to parse more CSS. Additionally, it can create unexpected inheritance, which can make your CSS harder to understand and maintain.
What are the alternatives to Sass Extend?
There are several alternatives to Sass Extend that can help you keep your CSS DRY. One of the most common alternatives is using Sass Mixins. Mixins allow you to define styles that can be reused throughout your stylesheet. Another alternative is using CSS custom properties, also known as CSS variables. These allow you to define specific values to reuse throughout your document.
How does Sass Extend differ from Sass Mixin?
While both Sass Extend and Mixin allow you to reuse code, they work in different ways. Extend works by grouping selectors that share the same properties, which can lead to more efficient but potentially bloated CSS. On the other hand, Mixin includes the styles where you include the mixin, which can lead to repetitive but more predictable CSS.
Can I use Sass Extend with media queries?
Yes, you can use Sass Extend within media queries. However, it’s important to note that the extend will only apply within the scope of the media query. This means that the extended styles will not apply outside of the media query.
How can I avoid the pitfalls of Sass Extend?
To avoid the pitfalls of Sass Extend, it’s important to use it sparingly and wisely. Avoid using it with deeply nested selectors or complex inheritance chains. Also, consider using alternatives like Mixins or CSS custom properties when they make more sense.
How does Sass Extend impact the performance of my website?
Sass Extend can potentially impact the performance of your website by creating bloated CSS. The browser has to parse all the CSS, and the more CSS there is, the longer it can take. This can lead to slower page load times, which can negatively impact user experience and SEO.
Can I use Sass Extend with pseudo-classes and pseudo-elements?
Yes, you can use Sass Extend with pseudo-classes and pseudo-elements. However, it’s important to note that the extended styles will only apply to the exact match of the selector, including the pseudo-class or pseudo-element.
What happens if I use Sass Extend with a complex selector?
If you use Sass Extend with a complex selector, Sass will generate all possible combinations of the selector, which can lead to bloated and hard-to-maintain CSS. It’s generally recommended to avoid using Extend with complex selectors.
Can I use Sass Extend in a modular CSS architecture?
While you can use Sass Extend in a modular CSS architecture, it’s generally not recommended. Extend can create unexpected inheritance and bloated CSS, which can make your code harder to understand and maintain. It’s often better to use alternatives like Mixins or CSS custom properties in a modular architecture.
Non-binary trans accessibility & diversity advocate, frontend developer, author. Real life cat. She/her.