What Nobody Told You About Sass’s @extend

Kitty Giraudel
Kitty Giraudel
Share

Sass provides a lot of powerful features to write consistent and robust CSS. One of the most powerful yet tricky ones has to be @extend. While most Sass users understand the basics of @extend, I feel there are some more obscure parts to it that are not as well known.

But first, let’s start with the basics.

The Basics of @extend

The Sass @extend directive provides a simple way to allow a selector to inherit the styles of another one. This is especially useful in component-based CSS architecture, allowing us to easily make variations from a component by extending it. Many of you might already know how to extend a selector but for those who don’t, here is the most basic case of selector extension in Sass:
.message {
  padding: .5em;
}

.message-error {
  @extend .message;
}

Resulting in the following CSS:

.message, .message-error { 
  padding: .5em;
}

Easy, right? Of course you’re not limited to extending classes; you can extend tag selectors (e.g. a), IDs (e.g. #id) — basically any valid CSS selector. And you can extend Sass placeholders (e.g. %placeholder) which are explicitly made for this.

Note: In a previous article, Sass: Mixin or Placeholder?, I covered the topic of placeholder in more detail. You can also check out placeholder on the official Sass docs.

Extending a deeper selector

In most cases you will extend very simple selectors (mostly classes), but nothing prevents you from extending a more complex selector like .class element:pseudo or even:

.message + .message {
  margin-bottom: .5em;
}

.message-error {
  @extend .message;
}

Which would output:

.message + .message, 
.message-error + .message-error, 
.message + .message-error, 
.message-error + .message { 
  margin-bottom: .5em;
}

The result is now a little more complex, but if you understand @extend, the output shouldn’t surprise you too much.

Multiple extends

You might be aware that you can extend multiple selectors from within the same rule but did you know you can do so with a single @extend directive?

.message {
  padding: .5em;
}

.important {
  font-weight: bold;
}

.message-error {
  @extend .message, .important;
}

Output:

.message, .message-error {
  padding: .5em;
}

.important, .message-error {
  font-weight: bold;
}

If you ask me, I don’t prefer this method. While it makes the code a bit shorter, I find it it’s harder to read. Using distinct directives for each extended selector makes it easier for the eye to spot the selectors and how many are being extended.

You could work around the syntax like this:

.message-error {
  @extend
    .message,
    .important;
}

But how much more readable is that?

I will continue to use one @extend directive per line, but the result will be the same so feel free to pick the syntax you’re most comfortable with.

Chaining extends

As just discussed, a selector can @extend from multiple sources. But you can also chain your @extend directives:

.message {
  padding: .5em;
}

.message-important {
  @extend .message;
  font-weight: bold;
}

.message-error {
  @extend .message-important;
}

And the output:

.message, .message-important, .message-error {
  padding: .5em;
}

.message-important, message-error {
  font-weight: bold;
}

Although the above example is valid, I would advise against doing this since it can have undesired effects. Let’s discuss those right now.

Massive extending

Probably one the main arguments people make against CSS preprocessors is: “Look at the output, it’s horrifying!” In a way, they are right. The Sass @extend directive is so powerful it can result in unusually large extends.

This is why you should always be careful when extending a selector because it will extend all the occurrences of the selector — which can quickly get messy. Consider the following example:

.important {
  font-weight: bold;
}

.sidebar .important {
  color: red;
}

.message {
  @extend .important;
}

This will output:

.important, .message {
  font-weight: bold;
}

.sidebar .important, .sidebar .message {
  color: red;
}

As you can see, not only has .message inherited from .important but .message has also inherited from the instances where .important is used in a descendant selector too. While this can be what you’re expecting, this is not always the case, so you should use the @extend directive carefully. Either make sure the selector you’re extending from only exists in one place in your CSS, or extend a placeholder — that’s what they’re for.

So keep in mind that Sass doesn’t create bad code, bad coders do.

Preserving source order

One very little known characteristic of @extend in Sass is the way it deals with source order. Let’s have a look at the following code snippet:

.half-red {
  color: rgba(red, .5);
}

.message-error {
  color: red;
  @extend .half-red;
}

Unsemantic class name aside, this is perfectly fine code, right? You would probably expect something like the following when it compiles:

/* This will not be the correct output! */
.message-error {
  color: red;
  color: rgba(255, 0, 0, 0.5);
}

This looks like a simple way to provide graceful degradation for older browsers that don’t support rgba() color values (we won’t expand on the reasons behind using an @extend here; it doesn’t matter). But this is not what the CSS output will look like. Au contraire, it will look like this:

.half-red, .message-error {
  color: rgba(255, 0, 0, 0.5);
}

.message-error {
  color: red;
}

But why?! you probably say unhappily. This is because the @extend directive works in the reverse way that you would expect. From the Sass documentation:

@extend works by inserting the extending selector […] anywhere in the stylesheet that the extended selector […] appears.

In our example, the “extending selector” is .message-error and the “extended selector” is .half-red.

Optional extends

Depending on the kind of application you’re working on, you may or may not be using third-party frameworks or CSS-based widgets. If some of these are written in Sass, there is nothing preventing you from extending selectors from those files.

Unfortunately if the extended selector happens to be missing, Sass will throw an error and the compilation will fail.

“.message-error” failed to @extend “.important”.
The selector “.important” was not found.
Use “@extend .important !optional” if the extend should be able to fail.

As you can see, you can pass an !optional flag to your imports to prevent them from failing if ever the extended selector is not found. This is useful also in the case where the extending and the extended selectors are conflicting:

a.important {
  font-weight: bold;
}

p.message-error {
  @extend .important;
}

Indeed, this throws an error because both selectors are specified, making them impossible to unify. Thus you’ll get the following error:

No selectors matching “.important” could be unified with “p.message-error”.

Adding the !optional flag to your @extend would resolve this issue.

Extending and media queries

One of the biggest issues with @extend is its lack of support for extending from within a @media directive. Unfortunately, Sass doesn’t allow cross-media extensions:

.important {
  font-weight: bold;
}

@media (max-width: 767px) {
  .message-error {
    @extend .important;
  }
}

This will result in the following error:

You may not @extend an outer selector from within @media.
You may only @extend selectors within the same directive.

This is because @extend is basically about moving selectors around and not CSS rules as we saw in the section on preserving source order. If Sass allowed this, then extending a selector that is inside of another media query would produce something like this:

.rule, @media(max-width: 767px) { .another-rule }, .another-another-rule {
  /* ... */
}

… which is obviously not valid CSS.

That being said, Sass developers are well aware of this issue (demonstrated by the outrageous number of issues mentioning this on their repo: #501, #640, #915, #1050, #1083).

Since this is a major downside to advanced Sass architecture, they are likely to implement a solution to this problem soon. According to this comment from Nex3 (Sass lead developer), it will be mixin interpolation.

Best Practices for @extend

To sum up, here are what I would call best practices when using the @extend directive in Sass:

  • Make sure the extended selector is present only once in the stylesheet.
  • Avoid extending from nested selectors.
  • Avoid chaining @extend directives.
  • Don’t try extending from within a media query; it doesn’t work.

That wraps up this discussion of @extend. If you have anything to add, let me know in the comments!

This article was translated into French by Pierre Choffé for La Cascade

Frequently Asked Questions about Sass Extend

What is the purpose of the @extend directive in Sass?

The @extend directive in Sass is a powerful tool that allows one selector to inherit the styles of another selector. This means that you can create a base set of styles and then extend them to other selectors, reducing the amount of code you need to write and making your stylesheets more maintainable. It’s particularly useful when you have styles that are used in multiple places across your site, as it allows you to keep your code DRY (Don’t Repeat Yourself).

How does the @extend directive work in Sass?

The @extend directive works by telling Sass that one selector should inherit the styles of another. For example, if you have a .button class with certain styles and you want your .submit-button class to have the same styles, you can use the @extend directive like this:

.button {
background-color: blue;
color: white;
}

.submit-button {
@extend .button;
}

In the compiled CSS, .submit-button will have the same styles as .button.

What are the limitations of the @extend directive in Sass?

While the @extend directive is a powerful tool, it does have some limitations. One of the main limitations is that it can only extend simple selectors – it cannot extend complex selectors or selectors with pseudo-classes. Additionally, the @extend directive can lead to unexpected results if used incorrectly, as it can cause styles to be applied to more elements than intended.

Can I use the @extend directive with placeholder selectors?

Yes, the @extend directive can be used with placeholder selectors in Sass. Placeholder selectors, denoted by a %, are a special type of selector that are not output in the final CSS unless they are extended. This makes them ideal for defining base styles that are only used when extended.

How does the @extend directive compare to mixins in Sass?

The @extend directive and mixins in Sass both allow you to reuse styles, but they work in different ways. While @extend allows one selector to inherit the styles of another, mixins allow you to define styles that can be included in other selectors. Mixins can also accept arguments, allowing you to create more dynamic styles. The choice between @extend and mixins depends on your specific needs.

Can I extend multiple classes in Sass?

Yes, you can extend multiple classes in Sass. You can do this by including multiple @extend directives in your selector. For example:

.my-class {
@extend .class1;
@extend .class2;
}

This will cause .my-class to inherit the styles of both .class1 and .class2.

What happens if the target selector of an @extend directive is not found?

If the target selector of an @extend directive is not found, Sass will throw an error. This is because Sass needs to know which styles to apply to the extending selector. To avoid this error, make sure that the target selector is defined before it is extended.

Can I use the @extend directive in a media query?

Yes, you can use the @extend directive in a media query in Sass. However, it’s important to note that the @extend directive can only extend selectors that are in the same scope. This means that if you define a selector outside of a media query, you cannot extend it inside a media query, and vice versa.

Can I extend a selector that is defined in another file?

Yes, you can extend a selector that is defined in another file in Sass. To do this, you need to use the @import directive to import the file that contains the selector you want to extend. Once the file is imported, you can use the @extend directive as usual.

Can I use the @extend directive with the !optional flag?

Yes, you can use the @extend directive with the !optional flag in Sass. The !optional flag tells Sass to silently fail if the target selector is not found, instead of throwing an error. This can be useful if you want to extend a selector that may not always be present.