Why You Should Avoid Sass @extend

article

#1

Originally published at: http://www.sitepoint.com/avoid-sass-extend/

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?

Continue reading this article on SitePoint


#2

Very eye-opening article, Hugo, but here's a good potential use-case for placeholders and @extend: http://sassmeister.com/gist/08aee2e0b393924cec84

Basically, the combination of placeholders and @extend can be used to flexibly establish relationships between abstract components, especially when the selectors used to represent these components can change without ever having to change the underlying CSS.


#3

Nice article. I've already thought about switching to includes in my future projects, as my co-worker has trouble reading the long selectors in the dev-tools (i.e. clearfix, which is extended 10 times).

One big advantage of @extend is, however, that you may reuse pure CSS components from external projects without the need of modifying them. As I don't wanna have some hybrid approach, where i sometimes use @extend and sometimes @include to extend - would it make sense to create an extend mixin like this?

http://sassmeister.com/gist/dbe1cb5a682f3d695395


#4

Great idea using @extend to handle relationships between parent and child abstract components. Theoretically you're right, but have you already used this in a real project?

In my projects I usually have
- some base css applying everywhere (normalize, default styling of forms/tables/links, etc. )
- some external css (from plugins like sliders, overlays, ...)
- a set of helper classes/placeholders (i.e. clearfix, visuallyhidden, ...), which i may extend/include (deciding to switch) wherever needed
- a grid / structure css, which defines the general structure / areas of my templates
- abstract components, which i may extend/include (deciding to switch as well)
- components

Up to now, I've never had the use case of using nested abstract components. Yes, some of the "abstract components" are used within others, but it was never necessary to ensure, that they only work within a certain parent class.


#5

Thanks @MattDiMu - our development team is using this technique (and an architectural structure based on this) in the next version of our project, which is a large front-end application.

By considering that everything is a component, we have found that component nesting is inevitable, and that it's important to maintain parent-child component relationships in a way that's flexible and thoughtful of separation of concerns.

Another reason you may want to use %placeholders is to maintain coherency within your styles - something that is neglected as soon as you treat your components as isolated modules (which, unfortunately, seems to be the trend nowadays).

As a contrived example, let's say you have a %panel component that has a certain look and feel, and you want a %modal component to use the panel's style (for coherency), but also to add other styling, such as fixed positioning and an overlay. By using @extend with placeholders, you have the flexibility to:

  • Change the styling of the %panel and have it automatically be reflected in the %modal, which was your original intention, and...
  • Use any selector nomenclature you want for %panel and %modal, such as .foo-panel and .bar-modal, with the ability to change these without affecting the parallel relationship between the two.

Can you use mixins for this? Of course, but they will become more complex and unwieldy when you have more complex relationships between components. You'll have to add more arguments, and even if you decide to extend %panel within a @mixin modal(...), you remove the ability to create a %panel-slightly-modified component that will @extend %panel, should you ever want to have a %modal-slightly-different component that can @extend %panel-slightly-modified.

This is advanced usage, though, and we are fully cognizant of how @extend and %placeholders work, and use techniques to prevent them from causing selector explosion. For everyday usage, Hugo's article brings up some very good points, and if you aren't aware of the effects of extending placeholders, you should opt for a @mixin solution instead.


#6

I agree. Very good use case. There are a lot of things I choose not to extend because it causes selectors to jump up the stylesheet and that is rarely a good thing. I'd rather only extend within a sass partial for things like buttons, panels, or other components. Then I know the selector will never jump up further in the output stylesheet than I intend.


#7

Thx for your reply. I see your point and have to admit, that I also tend to treat components as isolated modules wink

To keep coherency within my components, I use(d) to @extend multiple abstract placeholders, but i never tried to nest them. Might be useful sometimes, but also brings in more complexity.


#8

It is good to read more articles about the gotchas with @extend. The use case for relationships in the comments is interesting too.

I do have two questions though, re 'Extending is not necessarily better than mixins regarding performance':

1) We have come to learn that .unnecessary .levels .of .class .specificity can be detrimental to performance - do lists/combined selectors suffer the same? (By which I mean the .really, .long, .class, .groupings, . caused, .by @extend)

2) What if you are not gzipping?


#9
  1. Nope, you do not suffer from any performance drawback with lists of selectors. The performance bottleneck is having to find a selector deeply nested for the parser because it goes from right to left. So for instance .a .b .c would mean finding all .c elements, then filtering them to keep only the one in a .b element. Then filter them to keep only those within a .a element, and so on. You don't have to do this with lists of selectors since they are completely unrelated.

  2. You're obviously doing things wrong then. wink


#10

Thanks @HugoGiraudel, that's really insightful. Unfortunately, as a freelancer, I do not always have a say in the server setup so gzip is not always possible, hence my question. It strikes me, then, that very cautious/careful use of @extend could still be beneficial in these less-than-ideal instances?


#11

Gzip (or MOD_DEFLATE) is probably the biggest change you can make to a site to boost performance. I highly suggest you try using Gzip at all cost before considering optimising your CSS.


#12

Agreed but, as I said, not always within my power. Thanks for your article/comments.


#13

AFAIK, Browsers use CSS-Selectors slightly different: The Browser goes through its DOM-Elements and then tries to find matching selectors for this DOM-Element. As soon as one of the selectors doesn't fit (from right to left), the whole nested selector is skipped.

e.g. you have the following nested selector:

.a .b .c .d .e .f { property: value; }

and 100 DOM-Elements of which 4 have the class f and one of these four has parents with the classes a b c d and e. Then the Browser will
- 96 times only check the .f selector and skip it afterwards
- 4 times check the .e selector
- 1 time check the .d selector
- 1 time check the .c selector
- 1 time check the .b selector
- 1 time check the .a selector


104 checks are necessary

Even if you'd optimise your selector and reuduce it to

.d .e .f { property: value; }

it wouldn't make a significant difference, as still 101 checks would be necessary.

So even you've reduced the length of your selector from 6 to 3 individual selectors (-50%), you've reduced the amount of checks only by 2,8%.

Conclusio: Nested selectors are no performance bottleneck as long as your very right selector doesn't match too many elements (use classes).


#14

Gzip compression is used on 58.3% of websites* leaving 41.7% un-gzipped.
IMO this takes the gzip-will-fix-it argument of the table...
It would have been nice – but so would putting IE8+9 in the grave wink