As useful as mixins can be, they also can have a bit of a downside. Notice that each instance of mixin creates a duplication of the ruleset in our compiled CSS:
|
|
It's the same thing, over, and over! And the heading-shadow mixin is just about as minimal as you can get: it only contains a single property. But, that’s not always the case. If we were to make a mixin for a set of typographic rules, we would end up with a whole lot more:
@mixin typography {
color: $colour-primary;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
And there could be even more than that: font-kerning
, line-height
, text-transform
, the list goes on. Now let’s say we include this typography mixin a bunch of times. When you look at our Sass, it looks pretty clean:
@mixin typography {
color: $colour-primary;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
h1 {
@include typography;
}
textarea {
@include typography;
}
button {
@include typography;
}
input {
@include typography;
}
However, when you check out the compiled CSS, you find a lot of duplicate code:
h1 {
color: #15dea5;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
textarea {
color: #15dea5;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
button {
color: #15dea5;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
input {
color: #15dea5;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
That can make finding a particular block of code trickier, as all of the repetition can make it hard to discern the block that you’re searching for out of all of the similar looking blocks. And your mouse-scrolling finger can only take so much abuse.*
Sure, you could turn the typography mixin into a selector and assign it to elements that need it in the HTML:
|
|
But that totally screws up the BEM naming conventions. So, a mixin produces a lot of redundant code, and a normal, utilitarian selector doesn’t work with BEM. Seems kind of like a "darned if you, darned if you don’t" sort of situation, right? The answer can be found somewhere in the middle: the Sass extension.
Extensions are a lot like mixins: you write a block of code and leverage Sass to reuse it, saving you from retyping it over and over again. Unlike mixins, you don’t need to declare it with a special identifier - just write it as a simple selector:
.typography {
color: #15dea5;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
And then, to extend a selector to .typography
, type @extend followed by the selector name, .typography
:
.typography {
color: $colour-primary;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
h1 {
@extend typography;
}
Try it out for yourself!
Our website is full of different pages and layouts, such as the .form
or .proj-prev
blocks. Each of those blocks needs a heading element, which should receive the same typographic rules. We could use mixins to take care of this, but that can lead to a bunch of repetitive code in compiled CSS.
Instead, we can create a .heading-typography
selector and use it to hold all of the styles we need for our heading elements, and then extend the heading selectors to .heading-typography
. Try it out in this interactive exercise:
Create a
.heading-typography
selector and give it some typographic properties, such asfont-size
,font-style
,color
, etc.Use nesting and the ampersand to create heading elements for the
.form
and.proj-prev
blocksUse the
@extend
keyword to extend the new.form__heading
and.proj-prev__heading
elements to.heading-typography
Review the rendered HTML. Both rendered blocks should reflect the typographic properties and values set in
.heading-typography
Once you're done, check your answer with this CodePen!
Using placeholders
Notice that I said “extend to,” and not just “extend” in the section above? That’s because you’re not extending the .typography-content
selector! What you’re actually doing is extending the h1
selector to .typography
.
And what, exactly, does that mean? 😧
Let’s take a look at the compiled CSS for some clarification:
.typography, h1 {
color: #15dea5;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
Notice that you don’t see any of the code from the extend within the h1 selector. And when you look at .typography
, you see that Sass has added h1
to the list of selectors for the ruleset.
With mixins, Sass includes the contents of the mixin where ever it is evoked, which is where all of that duplicate code comes from. But, when you use @extend
on h1
, you’re telling Sass that rather than duplicating the properties, you want h1
to use the ruleset from another selector; in this case, .typography
. So Sass “extends” h1
across the sheet to .typography
’s rules, and adds it to the list.
If you deploy @extend
.typography
to a bunch of other selectors, you won’t have any duplicated rulesets. Instead, you'll end up with all of those selectors added to the list with .typography
:
|
|
Now, when you look at the list of selectors that follow .typography
, you see a lot of elements we are using throughout our code. Except for .typography
, that is. That’s because its purpose isn’t to style an element, but rather serve as a placeholder for other selectors to be extended to.
Sure, you could rename it to something like.placeholder-typography
, but having selectors in your CSS file that aren’t actually being used anywhere is a bad idea. Unused selectors needlessly increase file size, and clutter things up. And that can make deciphering what’s going on a lot more confusing in the future.
Instead, Sass has a built-in placeholder that you can use to hold your ruleset, rather than a standard selector:
%typography {
color: $colour-primary;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
Prefixing the selector with a percent sign ( % ), rather than the standard period of class selectors, creates a Sass placeholder.
Sass placeholders, also called “silent classes” and "placeholder classes," can be extended to, just like a selector. But unlike a standard class selector, when you use placeholders:
%typography {
color: $colour-primary;
font-size: 2rem;
font-weight: 100;
line-height: 1.7;
}
h1 {
@extend %typography;
}
textarea {
@extend %typography;
}
button {
@extend %typography;
}
input {
@extend %typography;
}
By only rendering the selectors extending to the placeholder, your CSS is cleaner and more explicit. There are no placeholder selectors to create confusion, and, other than the list of extended selectors, no duplicate code.
Try it out for yourself!
Extending our heading elements to .heading-typography
is working just fine, but it does result in a class selector that doesn’t actually get used by any of the blocks or elements on our site. Instead, .heading-typography
simply exists for .form__heading
and .proj-prev__heading
to extend to.
Having an unused selector in our CSS could be confusing to future-us, as we might not remember that we are using it to extend other elements to. So, rather than compile useless selectors, let’s change .heading-typography
from a class selector to a placeholder. In this interactive exercise:
Convert
.heading-typography
from a class selector to a placeholder by swapping the prefixing “.” to a percent sign (%)Update
.form__heading
and.proj-prev__heading
so that they extend to the new%heading-typography
placeholder, rather than the.heading-typography
selector that no longer exists
Review the rendered HTML to ensure that nothing has visually changed about the page and check your answer with this CodePen.
Cool, right? But when should you use mixins vs extensions? Coming up next, we’ll take a closer look at the similarities and differences between the two and figure that out.
Let's recap!
Extensions serve a similar purpose to mixins: both save you from writing a bunch of duplicate code.
Where mixins compile to duplicate rulesets, extensions compile to duplicate selectors.
To prevent unused selectors in your codebase, you can use placeholder rulesets by prefixing its name with a percent symbol ( % ):
%extend-placeholder