101

In Sass, I can't quite discern the difference between using @include with a mixin and using @extend with a placeholder class. Don't they amount to the same thing?

temporary_user_name
  • 35,956
  • 47
  • 141
  • 220
  • 2
    include doesn't give you extend base class, it just add options. Just advise you to read http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#including_a_mixin and http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#extend – CodeGroover Aug 02 '13 at 04:28
  • 1
    Also, @CodeGroover, not a useful comment whatsoever, maybe you misunderstood the question. Reading this gives more useful information: https://gist.github.com/antsa/970172 – temporary_user_name Aug 03 '13 at 00:04
  • 4
    **Any time you'd use a mixin with no parameter, an extend will be more efficient.** [Courtesy Chris](https://css-tricks.com/the-extend-concept/) – Abhijeet Jan 19 '16 at 06:03

5 Answers5

94

Extends do not allow customization, but they produce very efficient CSS.

%button
  background-color: lightgrey
  &:hover, &:active
    background-color: white

a
  @extend %button

button
  @extend %button

Result:

a, button {
  background-color: lightgrey;
}
a:hover, button:hover, a:active, button:active {
  background-color: white;
}

With mixins, you get duplicated CSS, but you can use arguments to modify the result for each usage.

=button($main-color: lightgrey, $active-color: white)
  background-color: $main-color
  border: 1px solid black
  border-radius: 0.2em

  &:hover, &:active
    background-color: $active-color

a
  +button

button
  +button(pink, red)

Results in:

a {
  background-color: lightgrey;
  border: 1px solid black;
  border-radius: 0.2em;
}
a:hover, a:active {
  background-color: white;
}

button {
  background-color: pink;
  border: 1px solid black;
  border-radius: 0.2em;
}
button:hover, button:active {
  background-color: red;
}

Please follow this consecutive set of code examples to see how you can make your code cleaner and more maintainable by using extends and mixins effectively: http://thecodingdesigner.com/posts/balancing

Note that SASS unfortunately does not allow using extends inside media queries (and corresponding example from the above link is wrong). In the situation where you need to extend based on media queries, use a mixin:

=active
  display: block
  background-color: pink

%active
  +active

#main-menu
  @extend %active // Active by default

#secondary-menu
  @media (min-width: 20em)
    +active // Active only on wide screens

Result:

#main-menu {
  display: block;
  background-color: pink;
}

@media (min-width: 20em) {
  #secondary-menu {
    display: block;
    background-color: pink;
  }
}

Duplication is inevitable in this case, but you shouldn't care too much about it because web server's gzip compression will take care of it.

PS Note that you can declare placeholder classes within media queries.

Update 2014-12-28: Extends produce more compact CSS than mixins do, but this benefit is diminished when CSS is gzipped. If your server serves gzipped CSS (it really should!), then extends give you almost no benefit. So you can always use mixins! More on this here: http://www.sitepoint.com/sass-extend-nobody-told-you/

hlovdal
  • 26,565
  • 10
  • 94
  • 165
Andrey Mikhaylov - lolmaus
  • 23,107
  • 6
  • 84
  • 133
  • 2
    I don't think that't *entirely* accurate...you can customize `@extends` by overriding the extension parent. Of course you can't pass in arguments, but then is that the only difference? In that case, is `@extend` just `@mixin` without arguments? I still don't see the advantage or the difference. – temporary_user_name Aug 02 '13 at 23:46
  • 2
    Here are a few other quirks ... http://stackoverflow.com/questions/30744625/sass-and-bootstrap-mixins-vs-extends/30744854#30744854 – Toni Leigh Jan 26 '16 at 12:37
  • I'd be careful interpreting the "gives you almost no benefit" aspect of the last paragraph, there. Gzip compression is a dictionary-based Huffman coder, so if the repeat happens far enough apart, the text will not be present in the dictionary and compression ratios will suffer. I still always prefer `@extend` where possible, as this _will_ produce more compact CSS, which will still compress fairly well (it's ASCII text, after all). – amcgregor Jul 24 '19 at 19:18
  • @amcgregor, the difference is negligible. – Andrey Mikhaylov - lolmaus Jul 25 '19 at 15:04
  • @AndreyMikhaylov-lolmaus I agree! I'd expect the difference to be essentially unmeasurable over the wire, regardless of choice for anything up to a megabyte or so of generated CSS, other than the fact that the uncompressed final result in memory will be more compact using `@extend`. This is a micro-optimization seemingly based on intuition and gut feelings, rather than an understanding of how the compression scheme involved actually works. (Also: it ignores the considerable overhead of on-demand gzip transfer-encoding; compression isn't free! ;) – amcgregor Jul 25 '19 at 19:26
  • @amcgregor The cost of compression is a matter of server configuration. A good server would cache the result and serve precompressed Brotli, which, by the way, is very efficient. You should do it regardless of whether you're using extends or mixins. – Andrey Mikhaylov - lolmaus Jul 26 '19 at 07:55
  • @amcgregor, that said, the usage of extends can cause you a lot of trouble with: 1. not being able to use in media queries; 2. not being able to customize per usage (no params); 3. getting bloated selectors if you fail to extend *only* from `%placeholder` classes and never from `.normal` classes. Thus, usage of extends over mixins has a very appreciable development/maintenance cost. – Andrey Mikhaylov - lolmaus Jul 26 '19 at 07:59
  • @amcgregor, whereas the cost of a few extra bytes in payload size is so negligible that it's not even worth the time developers spend on considering the optimization. That's unless you're Amazon.com for whom each additional millisecond of loading time costs a statistically significant amount of lost profit. – Andrey Mikhaylov - lolmaus Jul 26 '19 at 08:01
  • [How about all that HTML people write that they… simply don't need to?](https://gist.github.com/amcgregor/71c62ea2984839a9063232ed2c0adf27#file-minimal-html) HTML5Boilerplate (1611 byte index.html, 70.2KB transfer, 152KB total) vs. my own variant (including the same metadata): 622 bytes. Reduction to 38.6% original size (only counting `index.html` itself, before compression) I'd argue is more than "Amazon milliseconds". There will always be [combinatorial hell-cases](https://gist.github.com/amcgregor/e47872420843c5ea5b8b0227a42fc39a), but… this is not inevitable. Just lazy. (Sadly, real…) – amcgregor Jul 27 '19 at 04:44
  • @amcgregor I wouldn't waste much time into optimizations that give a fixed win of a mere 1 KB. That's just not worth it. If you can do it quickly, do it. If it takes a lot of time and effort -- spend it on something more useful like business logic. – Andrey Mikhaylov - lolmaus Jul 27 '19 at 07:08
  • @amcgregor As for "combinatorial hell-cases", if you use `@extend` extensively, you will always end up like that. And selectors will occupy the most of screen area in you dev tools, making it hard to work. The cost you have to pay in wasted developer time is way more than lost profit from an extra millisecond of loading time (unless you're Amazon). – Andrey Mikhaylov - lolmaus Jul 27 '19 at 07:10
  • @amcgregor Hey, I have created a [mixin version](https://gist.github.com/lolmaus/d294d382adc99dbf8ceb5b43980149a2) of your "combinatorial hell" `@extend` example. And I used [this site](https://tools.paulcalvano.com/compression.php) to compare the compression. The `@extend` version won by 4 bytes in gzip mode and 1 byte in Brotli mode. ONE. BYTE. – Andrey Mikhaylov - lolmaus Jul 27 '19 at 13:21
  • @amcgregor If your CSS developer works 40h/week, earns $35/h and wastes 20 minutes per day because of the disadvantages of `@extend`, this means that extends over mixins cost you roughly $3000 per year per developer. And if you tell them to spend time to optimize a few more bytes out of CSS/HTML in some other way, it will also cost you real money (because dev salary will not spent be on useful features). No customer will ever thank you for omitting the ` – Andrey Mikhaylov - lolmaus Jul 27 '19 at 13:25
  • @AndreyMikhaylov-lolmaus Focusing specifically on that markup as if the user reads it is a red herring. Users _might_ notice that the page is loaded **the moment they release their mouse button**. [Generating HTML you don't need to](https://github.com/marrow/cinje/wiki/Benchmarks#python-37) (note `cinje_flush_first` there, TTFB) **can** impact user experience. Also increased carbon footprint, [in a similar vein to tabbed v. space-based indentation](https://medium.com/@amcgregor/your-code-style-guide-is-crap-but-still-better-than-nothing-c4d75f5b3c44#3f07). – amcgregor Oct 03 '19 at 15:42
  • @AndreyMikhaylov-lolmaus My latest app deployment has 0.5ms (±0.3ms) page generation times. I'm really not joking about the page being loaded before the user finishes clicking. Y'all mention salary, [I'll point out sales](https://www.fastcompany.com/1825005/how-one-second-could-cost-amazon-16-billion-sales). – amcgregor Oct 03 '19 at 15:44
  • @amcgregor Two points here. One: have you measured the actual impact of switching from extends to mixins on loading time? Two: have you measured how much money your business will lose from that difference? And a bonus question: how much revenue does you business have to make for this to be even worth discussing? Amazon is unique: they rely on billions of small sales, whereas most other businesses rely on a much fewer number of sales. Customers looking for unique products also have very different habits: if I really need something, I'll buy it regardless of the waiting time. – Andrey Mikhaylov - lolmaus Oct 04 '19 at 06:16
  • That article from early 2012 claims that Amazon would lose 1.6 billion in sales for one extra second of loading time. Amazon's total sales in 2011 were 48.08 billion. This means that one second amounts for over 3% of their sales. This is A LOT. If it's true, it means that a great part of sales are impulsive buys of unimportant goods. Otherwise, customers would not forget buying after one second. Is your business like that? If you're selling random junk to billions of customers, then maybe you should be using Sass extends (even though I haven't seen actual measurements proving they are faster). – Andrey Mikhaylov - lolmaus Oct 04 '19 at 06:23
  • Oh, and don't forget to compare the cost of a few extra **milli**seconds of loading time against the cost of rolling out every new feature a few days later (and those delays accumulate over years). When selectors in Dev Tools look like [this](https://i.imgur.com/s761zx3.png), developer productivity suffers a lot. – Andrey Mikhaylov - lolmaus Oct 04 '19 at 06:28
19

A good approach is to use both - create a mixin that will allow you lots of customisation and then make extends for common configurations of that mixin. For example (SCSS Syntax):

@mixin my-button($size: 15, $color: red) {
  @include inline-block;
  @include border-radius(5px);
  font-size: $size + px;
  background-color: $color;
}
%button {
  @include my-button;
}
%alt-button {
  @include my-button(15, green);
}
%big-button {
  @include my-button(25);
}

This saves you from calling the my-button mixin over and over. It also means you don't have to remember the settings for common buttons but you still have the ability to make a super unique, one-off button should you choose.

I take this example from a blog post I wrote not long ago. Hope this helps.

FreddyBushBoy
  • 557
  • 4
  • 8
16

In my opinion extends are pure evil and should be avoided. Here is why:

given the scss:

%mystyle {color: blue;}
.mystyle-class {@extend %mystyle}
//basically anything not understood by target browser (such as :last-child in IE8):
::-webkit-input-placeholder {@extend %mystyle}

The following css will be generated:

.mystyle-class, ::-webkit-input-placeholder { //invalid in non-webkit browsers
  color: blue;
}

When a browser doesn’t understand a selector, it invalidates the entire line of selectors. This means that your precious mystyle-class is no longer blue (for many browsers). What does this really mean? If at any time you use an extend where a browser may not understand the selector every other use of the extend will be invalidated. This behavior also allows for evil nesting:

%mystyle {color: blue;}
@mixin mystyle-mixin {@extend %mystyle; height: 0;}
::-webkit-input-placeholder {@include mystyle-mixin} 
//you thought nesting in a mixin would make it safe?
.mystyle-class {@extend %mystyle;}

Result:

::-webkit-input-placeholder, .mystyle-class { //invalid in non-webkit browsers
  color: blue;
}

::-webkit-input-placeholder {
  height: 0;
}

Tl;dr: @extend is perfectly ok for as long as you never use it with any browser spesific selectors. If you do, it will suddenly tear down the styles wherever you have used it. Try to rely on mixins instead!

clearfix
  • 467
  • 1
  • 5
  • 10
4

Use mixins if it accepts a parameter, where the compiled output will change depending on what you pass into it.

@include opacity(0.1);

Use extend (with placeholder) for any static repeatable blocks of styles.

color: blue;
font-weight: bold;
font-size: 2em;
d4nyll
  • 11,811
  • 6
  • 54
  • 68
0

I totally agree with the previous answer by d4nyll. There is a text about extend option and while I was researching this theme I found a lot of complaints about extend, so just have in mind that and if there is a possibility to use mixin instead of extend, just skip extend.

Nesha Zoric
  • 6,218
  • 42
  • 34