Tiffany B. Brown

29 March 2017

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 a, 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

  1. Use placeholder selectors / silent classes and extend those.
  2. Use @mixin.
  3. 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; }

.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.

Get CSS Master

Did you learn something from this blog post? You might like my book, CSS Master. It contains several nuggets of CSS joy like this one.

Buy now from SitePoint