No seriously: Don’t use @extend
Allow me to illustrate why — in most cases — using @extend
in Sass is a bad idea.
Consider this SCSS example.
h4 {
color: #c09;
font-size: 1.2rem;
font-weight: 100;
}
label {
@extend h4;
cursor: pointer;
}
It compiles to the following.
h4,
label {
color: #c09;
font-size: 1.2rem;
font-weight: 100; }
label {
cursor: pointer; }
That's pretty straightforward, right? Both h4
and label
will share the same styles. Let's add some more lines to our SCSS.
.header h4 {
font-weight: bold;
}
.footer h4 {
display: inline;
}
You might expect your SCSS output to look like this.
h4, label {
border-bottom: 3px solid #c09;
text-decoration: none;
font-weight: 100; }
label {
cursor: pointer; }
.header h4 {
font-weight: bold; }
.footer h4 {
display: block; }
But with @extend
, what actually happens is this.
h4, label {
color: #c09;
font-size: 1.2rem;
font-weight: 100; }
label {
cursor: pointer; }
.header h4, .header label {
font-weight: bold; }
.footer h4, .footer label {
display: inline; }
Using @extend
duplicates every instance of that selector. Every time you add a new ruleset for h4
, Sass will include label
as part of the selector chain. It's very unlikely that you want to do this in your CSS. And it's why you should avoid @extend
. It introduces specificity and inheritance problems, and increases your file size.
What to do instead
- Use placeholder selectors / silent classes and extend those.
- Use
@mixin
. - Use plain CSS.
Use placeholder selectors / silent classes
Placeholder selectors (also called silent classes) begin with a percent symbol %
. They're great for extending because they don't become part of your generated CSS until you extend them.
Yes, I know the title of this post is No seriously: Don't use @extend. But when used with a placeholder selector instead of a CSS selector, @extend
goes from “terrible idea” to “near genius” one.
Let's add an %h4
placeholder selector to our SCSS.
h4, %h4 {
color: #c09;
font-size: 1.2rem;
font-weight: 100;
}
label {
@extend %h4; // Extend the placeholder instead
cursor: pointer;
}
.header h4 {
font-weight: bold;
}
.footer h4 {
display: inline;
}
Now we get the CSS output we want.
h4,
label {
color: #c09;
font-size: 1.2rem;
font-weight: 100;
cursor: pointer; }
.header h4 {
font-weight: bold; }
.footer h4 {
display: inline; }
There's a small drawback to using placeholder selectors / silent classes: It takes discipline. Each placeholder selector should only be used once. Add another rule for %h4
, and you may introduce unintended side effects. It's very easy for teams to unintentionally mess this up.
Use @mixin
With @mixin
we can group a set of repeated CSS rules, and @include
the mixin anywhere we need them. In this case, we might create a mixin called level4-text
.
@mixin level4-text {
color: #c09;
font-size: 1.2rem;
font-weight: 100;
}
Let's @include
it in both our h4
and label
rulesets.
h4 {
@include level4-text;
}
label {
@include level4-text;
cursor: pointer;
}
.header h4 {
font-weight: bold;
}
.footer h4 {
display: inline;
}
That gives us the following output.
h4 {
color: #c09;
font-size: 1.2rem;
font-weight: 100; }
label {
color: #c09;
font-size: 1.2rem;
font-weight: 100;
cursor: pointer; }
.header h4 {
font-weight: bold; }
.footer h4 {
display: inline; }
Downside: repeated CSS declarations. If you are using gzip to serve your CSS assets, repeated CSS is less of an issue. Gzip is pretty good at removing redundant bytes during compression. It's almost like those extra 49 bytes don't exist.
However, there's a very good chance that your site's CSS isn't gzipped. In that case, we can optimize our CSS output by using CSS for input.
Use plain CSS
Sass has some fun features, but we don’t have to use them. The CSS/SCSS below also works.
h4, label {
color: #c09;
font-size: 1.2rem;
font-weight: 100;
}
label {
cursor: pointer;
}
.header h4 {
font-weight: bold;
}
.footer h4 {
display: inline;
}
Our CSS output looks remarkably like our input, minus some line breaks.
h4, label {
color: #c09;
font-size: 1.2rem;
font-weight: 100; }
label {
cursor: pointer; }
.header h4 {
font-weight: bold; }
.footer h4 {
display: inline; }
Bonus: we've saved a few bytes.
Better still: use a class selector. A class offers the most flexibility, since it's not limited to h4
and label
elements. But don't use @extend
with your class. The same problems with @extend
apply, regardless of the selector.