Mixins: the good, the bad, the ugly…

Michaël Vanderheyden
7 min readJan 9, 2020

--

Working with SCSS and LESS, I’ve become used to see people using mixins and I used quite a bunch of them myself. Over time I’ve seen them being used in several different ways, some of which I think aren’t good.

But let’s start with…

The good 😊

Mixins allow you to have a single definition for a group of properties that are used throughout your code with different values by simply replacing the mentioned values through variable parameters.

Thanks to this you don’t have to repeat the same code over and over again or don’t have to create multiple classes for each potential value you’d need.

An example I like for this is line-clamping…
While not a standard (yet), its prefixed version is currently well supported by many browsers, but requires to set multiple properties. Something you don’t want to copy over and over again. Moreover, the amount of lines you want to cut the text at is variable.

So a version of line-clamping without pre-processor to cut either after 2 or 3 lines would probably look like this:

CSS.clamp-2-lines {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.clamp-3-lines {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
HMTL<p class="clamp-2-lines">cut text after 2 lines</p>
<p class="clamp-3-lines">cut text after 3 lines</p>

Now imagine the same code if you need 10 variations of it.
With a mixin, this get’s much nicer

LESS.line-clamp(@lines) {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: @lines;
-webkit-box-orient: vertical;
}
.class-that-cuts-after-2-lines {
.line-clamp(2);
}
.class-that-cuts-after-3-lines {
.line-clamp(3);
}
SCSS@mixin line-clamp($lines) {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
}
.class-that-cuts-after-2-lines {
@include line-clamp(2);
}
.class-that-cuts-after-3-lines {
@include line-clamp(3);
}
HMTL<p class="clamp-2-lines">cut text after 2 lines</p>
<p class="clamp-3-lines">cut text after 3 lines</p>

The bad 😣

Cutting a single line of text also requires multiple properties that have to be used in combination. So the same logic could be applied, right?!

Static mixins

LESS.text-overflow() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.class-that-cuts-after-1-line {
.text-overflow();
}
SCSS@mixin text-overflow() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.class-that-cuts-after-1-line {
@include text-overflow();
}
HTML<p class="class-that-cuts-after-1-line">cut text after 1 line</p>

Well yes, we could, but in this case we don’t need any variable parameter. So in cases where pre-processors don’t provide any advantages, vanilla CSS* should in my opinion always be the preferred solution.

* Vanilla CSS is just CSS, vanilla making it absolutely clear that it’s CSS and not a superset of it like LESS or SASS (which are also “CSS”).

So simply replacing the mixin through a generic class makes both the code (LESS/SCSS) and the output (CSS) smaller and also easier to debug.

CSS.text-overflow {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
HTML<p class="text-overflow">cut text after 1 line</p>

Compatibility mixins

Ok so if there is no variable parameter, mixins should not be used?
Yes and no… There is one use case where I still use mixins sometimes: When I need a prefixed version of a property for browser support.

LESS.display-grid() {
display: -ms-grid;
display: grid;
}
.grid-template-rows(@value) {
-ms-grid-rows: @value;
grid-template-rows: @value;
}
.grid-template-columns(@value) {
-ms-grid-columns: @value;
grid-template-columns: @value;
}
.class-with-grid-layout {
.grid();
.grid-template-rows(auto 1fr);
.grid-template-columns(1fr 20rem 2fr);
}
SCSS@mixin display-grid() {
display: -ms-grid;
display: grid;
}
@mixin grid-template-rows(@value) {
-ms-grid-rows: @value;
grid-template-rows: @value;
}
@mixin grid-template-columns(@value) {
-ms-grid-columns: @value;
grid-template-columns: @value;
}
.class-with-grid-layout {
@include grid();
@include grid-template-rows(auto 1fr);
@include grid-template-columns(1fr 20rem 2fr);
}

Now you might argue, that this could be covered by a simple mixin, like this:

LESS.display-grid(@templateRow, @templateColumn) {
display: -ms-grid;
display: grid;
-ms-grid-rows: @templateRow;
grid-template-rows: @templateRow;
-ms-grid-columns: @templateColumn;
grid-template-columns: @templateColumn;
}
.class-with-grid-layout {
.grid(auto 1fr, 1fr 20rem 2fr);
}
SCSS@mixin display-grid(@templateRow, @templateColumn) {
display: -ms-grid;
display: grid;
-ms-grid-rows: @templateRow;
grid-template-rows: @templateRow;
-ms-grid-columns: @templateColumn;
grid-template-columns: @templateColumn;
}
.class-with-grid-layout {
@include grid(auto 1fr, 1fr 20rem 2fr);
}

That is indeed correct, but like there are good reasons to not use shorthands in CSS, there are also use cases where defining display, row and column template in one place will not work.

Autoprefixer logo

Whenever possible though, I will always prefer to stick to standard vanilla CSS here too and let tools like autoprefixer take care of adding the necessary prefixed versions for me.

The ugly 🤯

Did you notice, that if you leave out the parenthesis in LESS, a mixin is not to differentiate from a regular class. Well, it turns out you can do that (but please don’t)…
So the text overflow example above could also look like this:

LESS.text-overflow {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.class-that-cuts-after-1-line {
.text-overflow;
}
HTML<p class="class-that-cuts-after-1-line">cut text after 1 line</p>

That can quickly become a problem in big projects when people start using any class like a mixin, even when they are not intended that way.

Imagine for a second that you have a whole bunch of classes following a naming convention and there is some code targeting all elements that use those classes:

LESS[class^='icon-']::before,
[class*=' icon-']::before {
margin: 5px
}
.icon-class-1 {
width: 20px;
height: 20px;
}
HTML<img class="icon-class-1">

In this example, the image will indeed have a margin of 5px as intended.
Now consider the same example, but for whatever reason, you want to use the class as a mixin instead of in the HTML:

LESS[class^='icon-']::before,
[class*=' icon-']::before {
margin: 5px
}
.icon-class-1 {
width: 20px;
height: 20px;
}
.my-image {
.icon-class-1;
}
HTML<img class="my-image">

In the example above, the class attribute in HTML does not contain a value starting with icon- so the margin will not be applied.

This is one of the reasons I prefer SCSS over LESS since the language doesn’t allow such mix up.

CSS custom properties 😍

What do CSS custom properties aka. CSS variables have to do with mixins?

Well let’s get back to the first example we had:

LESS.line-clamp(@lines) {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: @lines;
-webkit-box-orient: vertical;
}
.class-that-cuts-after-2-lines {
.line-clamp(2);
}
.class-that-cuts-after-3-lines {
.line-clamp(3);
}
SCSS@mixin line-clamp($lines) {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
}
.class-that-cuts-after-2-lines {
@include line-clamp(2);
}
.class-that-cuts-after-3-lines {
@include line-clamp(3);
}
HMTL<p class="clamp-2-lines">cut text after 2 lines</p>
<p class="clamp-3-lines">cut text after 3 lines</p>

Once compiled, the CSS would look exactly like it was before you switched the repetitive parts for a mixin:

CSS.clamp-2-lines {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.clamp-3-lines {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
HMTL<p class="clamp-2-lines">cut text after 2 lines</p>
<p class="clamp-3-lines">cut text after 3 lines</p>

So there is nothing variable anymore and you get the same “big” code as before.
CSS variables allow you to do basically the same as a variable mixin but with two huge advantages:
- The variable can be changed at run time (e.g. through JavaScript)
- The output CSS used by your page is just as “small” as the coded one

So the example above would look like this:

CSS.line-clamp {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: var(--visible-lines);
-webkit-box-orient: vertical;
}
.class-that-cuts-after-2-lines {
--visible-lines: 2;
}
.class-that-cuts-after-3-lines {
--visible-lines: 3
}
HMTL<p class="clamp-2-lines line-clamp">cut text after 2 lines</p>
<p class="clamp-3-lines line-clamp">cut text after 3 lines</p>

Conclusion

So I would just summarize by saying that mixins were a great workaround (and still are if you still have to support IE) for something CSS was lacking before custom properties.
But when given the possibility (browser support), I prefer the pure CSS solution to a superset like LESS or SCSS.

--

--

Michaël Vanderheyden

Lead UI/UX Engineer @ Eviden | Passionate about martial arts | CSS is my dojo