Last week, Danny Lewandowski, commented on my post My Sass Mixins, Extends, and Functions, asking my thoughts on Sass Extends vs. Mixins.
Let me provide a little bit of context:
The post was about a Sass broilerplate that I’ve created and use with every project. — It’s great because I don’t have to reinvent the wheel every time I start a new project. I always have a “go to” set of code I can use and don’t have to waste my time with dumb project set-up tasks.
At first, it might seem like code bloat: adding extra code that you may or may not use? Oh contraire. My Sass library only compiles what you actually use in your code! Awesome!
So what’s the difference between the three, Sass functions, mixins and extends?
NOTE:
If you already know an extend, mixin, and function is, feel free to jump to the chase. But, who knows, you may learning something along the way. 🙂An Extend
At first glance, an extend looks like an ordinary class:
.heading {
font-family: sans-serif;
font-size: 22px;
font-weight: bold;
}
But, you can other classes reference it:
h1 {
@extend .heading;
}
h2 {
@extend .heading;
font-size: 18px;
}
h3 {
@extend .heading;
font-size: 16px;
}
Suddenly, the same styles that apply to .heading
also apply to your h1
, h2
, and h3
.
The rendered CSS looks like this:
.heading, h1, h2, h3 {
font-family: sans-serif;
font-size: 22px;
font-weight: bold;
}
h2 {
font-size: 18px;
}
h3 {
font-size: 16px;
}
There are a few things to be aware of when working with mixins. Any change to the .heading
class, automatically gets applied to every class extending it.
If you’re not careful, you could inadvertently royally mess up your code. When I’m using extends I like to prepend the class I’m extending with a %
. That serves as a visual reminder. Plus, any classes that begin with a %
aren’t rendered in your final CSS.
You’ll also notice that with my h2
s and h3
s, I wanted a different font-size
, so I had to override them. That doesn’t exactly make for clean code. — Granted, you could remove font-size
property from the .heading
definition and then be explicit about it within the h1
definition. But, I was trying to make a point: Extends may cause your code to behave unexpectedly.
Take this Sass code, for example:
%red {
color: red;
}
.one {
@extend %red;
}
.two {
color: green;
}
.three {
@extend %red;
}
The rendered CSS looks like this:
.one, .three {
color: red;
}
.two {
color: green;
}
Now, for the HTML:
<div class="one two">Some text</div>
What color is our text? See the confusion? You may look at this example and said, I would write code like that. It’s a dumb implementation. True. When it is this simplified it’s dumb, but when you start writing more complex classes, it’s incredibly easy to do. — By the way, the answer is green, since two
is listed in the HTML last.
A Mixin
Let me start by giving you an example:
@mixin heading() {
font-family: sans-serif;
font-size: 18px;
font-weight: bold;
}
When you want to reference it elsewhere in your code:
h1 {
@include heading;
}
NOTE:
One thing that I’ve always thought was a little strange is the fact that it’s called a mixin — and when you declare it, you use themixin
keyword. But, when you implement it, you use the keyword @include
. Oh well. Nobody asked me.If you’re familiar with other coding languages, a mixin looks like a function — and, in fact, you can pass parameters into them:
@mixin heading($size) {
font-family: sans-serif;
font-size: $size;
font-weight: bold;
}
Right off, that gives our code a little more flexibility.
h1 {
@include heading(22px);
}
You can also set a default value, so that you don’t have to pass in a parameter every single time.
@mixin heading($size: 18px) {
font-family: sans-serif;
font-size: $size;
font-weight: bold;
}
Within your rendered CSS, a mixin looks different. Let’s take this Sass, for example:
h1 {
@include heading(22px);
}
h2 {
@include heading(18px);
}
h3 {
@include heading(16px);
}
When rendered it looks like this:
h1 {
font-family: sans-serif;
font-size: 22px;
font-weight: bold;
}
h2 {
font-family: sans-serif;
font-size: 18px;
font-weight: bold;
}
h3 {
font-family: sans-serif;
font-size: 16px;
font-weight: bold;
}
There’s a lot more code. It basically takes the places where your include
is and copies and pastes, or includes the code from your mixin.
A Function
Moving on, a function may seem kind of like a mixin, but the difference is, it’s handling a snippet of code and not the whole class.
Here’s my most used function.
$browser-context: 16;
@function em($pixels, $context: $browser-context) {
@if (unitless($pixels)) {
$pixels: $pixels * 1px;
}
@if (unitless($context)) {
$context: $context * 1px;
}
@return $pixels / $context * 1em;
}
And its implementation:
h1 {
font-size: em(22px);
}
I’m creating the font size in ems by passing in a pixel value. I think in pixels. Pixels make sense to me, even though I know ems are the way of internet type. So, why not make the computer work for me? I can still think and work in pixels and let the computer do the conversion to ems for me.
Let’s look at another example:
@function path($src, $dir: img) {
$src: unquote($src);
@return url(unquote("..")/$dir/$src);
}
And its implementation:
.hero {
background: path('bg.jpg') center top no-repeat;
}
This path
function creates the full image path for me. — Otherwise, I’d have to write:
.hero {
background: path('../img/bg.jpg') center top no-repeat;
}
That might not seem like much of a difference, but add that up over time and consider all the background images paths used throughout the site! — Then, what happens if your file path changes? You have to go back through every single instance in your code and change the path, instead of simply changing the path in one place: your function.
The other point I wanted to make with this snippet: you’ll notice that I call the function path()
and pass in the image file name. Because the function only handles a snippet of my code, I can still use the other background
properties: center top no-repeat
without interfering.
Now what?
We’ve gone through a brief synopsis of how each work. Let’s go back to Danny’s question:
I wonder what your thoughts on the debate over mixins vs extends is? Seems that extends are very unpopular.
I decided to do a little digging. It would be easy for me to answer based on my personal assumptions and biases, but I never want to be that person. One of the values for this site is to celebrate differences. In code, there are multiple ways to do one thing. Sometimes (but not always), there’s an obvious right and wrong answer. Other times, you can make a case for a better answer. But, I want to make sure, I approach topics with an open mind — also understanding that best practices change as technology evolves.
I found two great articles, both by Harry Roberts of CSS Wizardry. Harry is a great thought leader on CSS. He wrote cssguideline.es, which is one of my favorite resources when it comes to naming things in CSS.
My biggest take-aways from these two articles were
Related Items Should be Grouped Together
You want related items to be grouped together. — This makes sense because the implications extend beyond programming. Who wants the contents of their kitchen pantry spread across their house? No one, you want them all grouped together. And if you wanted to more specific, you’d want cans together, cereal together, etc.
How does this apply practically to our Sass / CSS? Let’s say you have an h3
style. There’s no need for those styles to be applied to a button
because they’re unrelated. It’s makes it harder for you to maintain and update your code in the future.
An @extend
would associate those two items, grouping them together.
h3, button {
font-size: 16px;
font-weight: bold;
}
But, a @mixin
makes complete sense. Not only does that allow you to keep the items separate, it also provides the consistency within your code base that you’re looking for.
h3 {
font-size: 16px;
font-weight: bold;
}
button {
font-size: 16px;
font-weight: bold;
}
Let’s look at the proper use case for an @extend
. Here’s some Sass:
@extend %tag {
border-radius: 30px;
font-size: 14px;
padding: 5px 10px;
text-transform: uppercase;
}
.tag {
@extend %tag;
backgrond: gray;
}
.tag--alert {
@extend %tag;
background: red;
}
.tag--caution {
@extend %tag;
background: yellow;
}
Our CSS output will look like this:
.tag, .tag--alert, .tag--caution {
border-radius: 30px;
font-size: 14px;
padding: 5px 10px;
text-transform: uppercase;
}
.tag {
background: gray;
}
.tag--alert {
background: red;
}
.tag--caution {
background: yellow;
}
Yes, the items are all grouped together at the top, but that’s OK, because they’re related! They’re all tags. It makes sense that those items would be together.
D-R-Y
Dry is a popular programming concept that stands for “Don’t Repeat Yourself.” — but the point that Harry makes is: don’t repeat yourself. It’s not about completely avoiding repetition.
Repetition in a compiled system is not a bad thing: repetition in source is a bad thing.
If you think about it, that is true for programming across the board. Take PHP, for example, you’ll have includes for header and footer files. You’re not repeating yourself in source, but the system is. It will compile and include all those pieces, repeating those elements so you don’t have to.
Performance / File Size
Let’s take a look at performance and file size.
Organization and DRY code are great programming principles, but if the end result slows down your site, creating larger files, is it really worth it?
Actually, it was file size and performance that first turned me onto to @extend
s. It produced less code in my CSS files, so it seemed like a better option. …This is true, sort of.
Mixins are better than @extends when gzipped.
In fact, it’s almost as if @mixins
were made for gzipping.
NOTE:
What is gzip exactly? gzip is a type of file compression. It looks for repetitive elements within your code (COUGH like mixins) and compresses it.If you look at Harry’s post, “Mixins Better for Performance” he ran a few tests to prove his point. To summarize, briefly, he found:
- mixin.css came in at 108K
- extend.css came in at 72K
That means that mixin.css was 36K larger, that’s almost 150%!
BUT minified and gzipped:
- mixin.css came in at 12K
- extend.css came in at 18K
That’s a difference of 6K. Mixins suddenly went from being 1.5x larger, to 33.333% smaller!
To get the full benefit of mixins, you must minify and gzip your code.
You’ve convinced me. Now, what?
Good. Welcome to light. ?
- Use extends only where appropriate.
- Default to mixins.
- Minify and gzip your code. Here’s how.
The Conversation