DRY-ing Out Your Sass Mixins

One of the most powerful features of the CSS preprocessor Sass is the mixin, an abstraction of a common pattern into a semantic and reusable chunk. Think of taking the styles for a button and, instead of needing to remember what all of the properties are, having a selector include the styles for the button instead. The button styles are maintained in a single place, making them easy to update and keep consistent.

Article Continues Below

But far too often, mixins are written in such a way that duplicates properties, creating multiple points of failure in our output CSS and bloating the size of the file. Now, if you’re using Sass, you’re already on your way to creating leaner CSS than the average programmer. But if you’re as obsessed with performance as I am, any bloat is unacceptable. In this article, I’ll walk through how to take your Sass mixins to the next ultra-light level of CSS output.

Some background on Sass#section1

Sass acts as a layer between an authored stylesheet and the generated .css file that gets served to the browser, and adds many great features to help make writing and maintaining CSS easier. One of the things Sass lets stylesheet authors do is DRY out their CSS, making it easier to maintain. DRY, or “don’t repeat yourself,” is a programming principle coined in The Pragmatic Programmer that states:

Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.

CSS is not very good at DRY; common sets of properties get duplicated all the time by necessity (think buttons). If you were to create styles for a button with three types, those styles would either need to be repeated for each button type, or the properties that make up that button would need to be split across multiple selectors. This puts authors between a rock and a hard place. Rewriting the properties for each type means lots of copying and pasting of CSS (increasing file size), multiple points of failure, no single source of truth, and a very fragile component in the end. On the other hand, splitting the properties into multiple selectors means there is no single, authoritative representation of any one type of button in a system, each instead being scattered between two or more selectors. This adds fragility to our components, as they are now ambiguously defined.

Ideally, what we want is a way to define the core styles in a single place (without duplication), and a single selector with which the styles can be applied.

Why DRY?#section2

Why go through this trouble? In short, because DRY CSS can improve our site’s performance. When architecting a site, performance matters—from the image formats we choose to how we write our CSS selectors. This is especially true when talking about mobile, where something as basic to the web as an HTTP request can pose performance challenges. The long-held assumption that CSS file size doesn’t matter in the large scale of web performance doesn’t hold true when mobile users are faced with less than 100MB for total shared website cache. Every little bit of room that can be squeezed out of the cache counts.

The goal, then, is to create selectors that are maintainable in our Sass and our HTML, and whose CSS representation is as slim as possible to reduce its footprint in the cache.

Mixins and extends: two half-solutions#section3

Sass’s mixins provide the answer to one of these problems: they allow stylesheet authors to create a single place where core styles can be defined and referenced. Mixins can even take arguments, allowing for slight changes from one mixin call to another and enabling different types of the same pattern to be created. There is, however, a problem with just utilizing mixins: if given the chance, mixins will write out their properties each time they are called, bloating the output CSS. Mixins solve the single, authoritative representation part of DRY-ing out the Sass declarations, but often leave duplicated properties in the output CSS.

Sass introduces another concept that can help to DRY out our CSS: extends. Used through the @extend directive, extends allow a stylesheet author to say, “I want selector A to be styled like selector B.” What Sass then does is comma-separate selector A with selector B, allowing them to share selector B’s properties, and write the remaining properties like normal. Unlike mixins, extends cannot be given arguments; it is an all-or-nothing deal.

Sass#section4

.couch {
	padding: 2em;
	height: 37in;
	width: 88in;
	z-index: 40;  
}
  
.couch-leather {
	@extend .couch;
	background: saddlebrown;
}

.couch-fabric {
	@extend .couch;
	background: linen;
}

CSS#section5

.couch, 
.couch-leather, 
.couch-fabric {
	padding: 2em;
	height: 37in;
	width: 88in;
	z-index: 40;
}

.couch-leather {
	background: saddlebrown;
}

.couch-fabric {
	background: linen;
}

Extends solve the duplicated property and single selector problem in the output CSS, but stylesheet authors still must maintain two separate sets of styles in their Sass, and need to remember which properties need to be added to each component type—as if they had written two selectors to begin with.

Mixins and extends both solve half of the problem apiece. By combining mixins and extends with some creative architecture and a few interesting features of Sass, a truly DRY mixin can be created that will combine both halves into a single, unambiguous, authoritative representation—both in the way a pattern is used and maintained in Sass and in how the styles of a component are applied and represented in the output CSS.

DRY building blocks#section6

Four features of Sass comprise the cornerstone of building DRY mixins: placeholder selectors, map data types, the @at-root directive, and the unique-id() function.

Placeholder selectors#section7

Placeholder selectors are a unique kind of selector for use with Sass’s @extend directive. Written like a class, but starting with a % instead of a ., they behave just like a normal extend except that they won’t get printed to the stylesheet unless extended. Just like normal extends, the selector gets placed in the stylesheet where the placeholder is declared.

Sass#section8

%foo {
	color: red;
}

.bar {
	@extend %foo;
	background: blue;
}

.baz {
	@extend %foo;
	background: yellow;
}

CSS#section9

.bar, 
.baz {
	color: red;
}

.bar {
	background: blue;
}

.baz {
	background: yellow;
}

Maps#section10

Maps are a data type (like numbers, strings, and lists) in Sass 3.3 that behave in a similar way to objects in JavaScript. They are comprised of key/value pairs, where keys and values can be any of Sass’s data types (including maps themselves). Keys are always unique and can be retrieved by name, making them ideal for unique storage and retrieval.

Sass#section11

$properties: (
	background: red,
	color: blue,
	font-size: 1em,
	font-family: (Helvetica, arial, sans-serif)
);

.foo {
	color: map-get($properties, color);
}

At-root#section12

The @at-root directive, new to Sass 3.3, places contained definitions at the root of the stylesheet, regardless of current nesting.

Unique ID#section13

The unique-id() function in Sass 3.3 returns a CSS identifier guaranteed to be unique to the current run of Sass.

Creating a basic mixin#section14

Turning a pattern into a mixin requires looking to the core of the styles that make it up and determining what is shared and what comes from user input. For our purposes, let’s use a basic button as an example:

Sass#section15

.button {
	background-color: #b4d455;
	border: 1px solid mix(black, #b4d455, 25%);
	border-radius: 5px;
	padding: .25em .5em;
	
	&:hover {
		cursor: pointer;
		background-color: mix(black, #b4d455, 15%);
		border-color: mix(black, #b4d455, 40%);
	}
}

To turn this into a mixin, choose which properties are user-controlled (dynamic) and which are not (static). Dynamic properties will be controlled by arguments passed into the mixin, while static properties will simply be written out. For our button, we only want color to be dynamic. Then we can call the mixin with our argument, and our CSS will be printed out as expected:

Sass#section16

@mixin button($color) {
	background-color: $color;
	border: 1px solid mix(black, $color, 25%);
	border-radius: 5px;
	padding: .25em .5em;
	
	&:hover {
		cursor: pointer;
		background-color: mix(black, $color, 15%);
		border-color: mix(black, $color, 40%);
	}
}

.button {
	@include button(#b4d455);
}

This works well, but this will produce lots of duplicate properties. Say we want to create a new color variation of our button. Our Sass (not including the mixin definition) and output CSS would look like the following:

Sass#section17

.button-badass {
	@include button(#b4d455);
}

.button-coffee {
	@include button(#c0ffee);
}

CSS#section18

.button-badass {
	background-color: #b4d455;
	border: 1px solid #879f3f;
	border-radius: 5px;
	padding: .25em .5em;
}
.button-badass:hover {
	cursor: pointer;
	background-color: #99b448;
	border-color: #6c7f33;
}

.button-coffee {
	background-color: #c0ffee;
	border: 1px solid #90bfb2;
	border-radius: 5px;
	padding: .25em .5em;
}
.button-coffee:hover {
	cursor: pointer;
	background-color: #a3d8ca;
	border-color: #73998e;
}

There are a bunch of duplicated properties in there, creating bloat in our output CSS. We don’t want that! This is where the creative use of placeholder selectors comes in.

DRY-ing out a mixin#section19

DRY-ing out a mixin means splitting it into static and dynamic parts. The dynamic mixin is the one the user is going to call, and the static mixin is only going to contain the pieces that would otherwise get duplicated.

Sass#section20

@mixin button($color) {
		@include button-static;

	background-color: $color;
	border-color: mix(black, $color, 25%);
  
	&:hover {
		background-color: mix(black, $color, 15%);
		border-color: mix(black, $color, 40%);
	}
}

@mixin button-static {
	border: 1px solid;
	border-radius: 5px;
	padding: .25em .5em;
	
	&:hover {
		cursor: pointer;
	}
}

Now that we have our mixin broken up into two parts, we want to extend the items in button-static to prevent duplication. We could do this by using a placeholder selector instead of a mixin, but that means the selectors will be moved in our stylesheet. Instead, we want to create a placeholder dynamically in place, so that it gets created the first time the selector is needed and retains the source order we expect. To do this, our first step is to create a global variable to hold the names of our dynamic selectors.

Sass#section21

$Placeholder-Selectors: ();

Next, in button-static, we check to see if a key exists for our selector. We will call this key “button” for now. Using the map-get function, we will either get back the value of our key, or we will get back null if the key does not exist. If the key does not exist, we will set it to the value of a unique ID using map-merge. We use the !global flag, since we want to write to a global variable.

Sass#section22

$Placeholder-Selectors: ();
// ...
@mixin button-static {
	$button-selector: map-get($Placeholder-Selectors, 'button');
	
	@if $button-selector == null {
		$button-selector: unique-id();
		$Placeholder-Selectors: map-merge($Placeholder-Selectors, ('button': $button-selector)) !global;
	}
	
	border: 1px solid;
	border-radius: 5px;
	padding: .25em .5em;
	
	&:hover {
		cursor: pointer;
	}
}

Once we have determined whether an ID for our placeholder already exists, we need to create our placeholder. We do so with the @at-root directive and interpolation #{} to create a placeholder selector at the root of our directory with the name of our unique ID. The contents of that placeholder selector will be a call to our static mixin (recursive mixins, oh my!). We then extend that same placeholder selector, activating it and writing the properties to our CSS.

By using a placeholder selector here instead of extending a full selector like a class, these contents will only be included if the selector gets extended, thus reducing our output CSS. By using an extension here instead of writing out the properties, we also avoid duplicating properties. This, in turn, reduces fragility in our output CSS: every time this mixin gets called, these shared properties are actually shared in the output CSS instead of being roughly tied together through the CSS preprocessing step.

Sass#section23

$Placeholder-Selectors: ();
// ...
@mixin button-static {
	$button-selector: map-get($Placeholder-Selectors, 'button');
	@if $button-selector == null {
		$button-selector: unique-id();
		$Placeholder-Selectors: map-merge($Placeholder-Selectors, ('button': $button-selector)) !global;
	  
		@at-root %#{$button-selector} {
			@include button-static;
		}
	}
	@extend %#{$button-selector};   
	
	
	border: 1px solid;
	border-radius: 5px;
	padding: .25em .5em;
	
	&:hover {
		cursor: pointer;
	}
}

But wait, we’re not quite done yet. Right now, we are still going to get duplicated output, which is something we don’t want (and we’re going to get a selector extending itself, which we also don’t want). To prevent this, we add an argument to button-static to dictate whether to go through the extend process or not. We’ll add this to our dynamic mixin as well, and pass it through to our static mixin. In the end, we’ll have the following mixins:

Sass#section24

$Placeholder-Selectors: ();

@mixin button($color, $extend: true) {
	@include button-static($extend);
	
	background-color: $color;
	border-color: mix(black, $color, 25%);
	
	&:hover {
		background-color: mix(black, $color, 15%);
		border-color: mix(black, $color, 40%);
	}
}

@mixin button-static($extend: true) {
	$button-selector: map-get($Placeholder-Selectors, 'button');
	
	@if $extend == true {
		@if $button-selector == null {
			$button-selector: unique-id();
			$Placeholder-Selectors: map-merge($Placeholder-Selectors, ('button': $button-selector)) !global;
			
			@at-root %#{$button-selector} {
				@include button-static(false);
			}
		}
		@extend %#{$button-selector};
		}
		@else {
		border: 1px solid;
		border-radius: 5px;
		padding: .25em .5em;
		
		&:hover {
			cursor: pointer;
		}
	}
}

After all this effort, we have created a way to easily maintain our styling in Sass, provide a single selector in our HTML, and keep the total amount of CSS to a minimum. No matter how many times we include the button mixin, we will never duplicate our static properties.

The first time we use our mixin, our styles will be created in the CSS where the mixin was called, preserving our intended cascade and reducing fragility. And since we allow multiple calls to the same mixin, we can easily create and maintain variations in both our Sass and our HTML.

With this written, our original example mixin calls now produce the following CSS:

Sass#section25

.button-badass {
	@include button(#b4d455);
}

.button-coffee {
	@include button(#c0ffee);
}

.button-decaff {
	@include button(#decaff);
}

CSS#section26

.button-badass {
	background-color: #b4d455;
	border-color: #879f3f;
}
.button-badass, 
.button-coffee, 
.button-decaff {
	border: 1px solid;
	border-radius: 5px;
	padding: .25em .5em;
}
.button-badass:hover, 
.button-coffee:hover, 
.button-decaff:hover {
	cursor: pointer;
}
.button-badass:hover {
	background-color: #99b448;
	border-color: #6c7f33;
}

.button-coffee {
	background-color: #c0ffee;
	border-color: #90bfb2;
}
.button-coffee:hover {
	background-color: #a3d8ca;
	border-color: #73998e;
}

.button-decaff {
	background-color: #decaff;
	border-color: #a697bf;
}
.button-decaff:hover {
	background-color: #bcabd8;
	border-color: #857999;
}

Our static properties get comma-separated in place where they were defined, which makes debugging easier, preserves our source order, and reduces our output CSS file size—and only the properties that change get new selectors. Nice, DRY mixins!

Going further#section27

Now, rewriting this same pattern over and over again for each mixin isn’t DRY at all; in fact, it’s quite WET (“write everything twice”—programmers are a silly bunch). We don’t want to do that. Instead, think about creating a mixin for the placeholder generation, so you can call that instead. Or, if using the Toolkit Sass extension (either through Bower or as a Compass extension), the dynamic-extend mixin can be used to find, create, and extend a dynamic placeholder. Simply pass it a string name to search, like “button.”

Sass#section28

@import "toolkit";

@mixin button($color, $extend: true) {
	@include button-static($extend);
	
	background-color: $color;
	border-color: mix(black, $color, 25%);
	
	&:hover {
		background-color: mix(black, $color, 15%);
		border-color: mix(black, $color, 40%);
	}
}

@mixin button-static($extend: true) {
	$button-selector: map-get($Placeholder-Selectors, 'button');
	
	@if $extend == true {
		@include dynamic-extend('button') {
			@include button-static(false);
		}
	}
	@else {
		border: 1px solid;
		border-radius: 5px;
		padding: .25em .5em;

		&:hover {
			cursor: pointer;
		}
	}
}

With this, you can DRY out your DRY mixin pattern, thus reducing your input Sass files as well as your output CSS files, and ensuring you are meta-programming at your very best.

About the Author

Sam Richard

Sam Richard, better known as Snugug throughout the Internet, is a developer with design tendencies and a love of building open source tools to help with both. He is the author of North, a chair of SassConf, and an accomplished bacon connoisseur.

30 Reader Comments

  1. The reason not to create a placeholder first is A) you need to maintain the placeholder separately from the mixin and B) in the simple examples given, while it doesn’t make much of a difference, when creating large systems, extends get populated where the placeholder is extended. As such, if you define the placeholder anywhere but exactly where you want it to appear in the source order, it’s going to be drastically out of place and may cause cascading issues. Creating it dynamically when the mixin is called ensures it lives in the correct place in your cascade.

  2. With the final version of the mix-in, why wasn’t `$button-selector: map-get($Placeholder-Selectors, ‘button’)` inside the `@extend == true clause`?

    Wouldn’t that be an unnecessary `map-get` operation?

  3. No matter how much I love over-engineering Sass, I have to say I am with Adam Moore on this one: manually creating the placeholder is enough in most cases. It keeps the code base simpler and won’t do much harm in almost every case.

    That being said, the final code is pretty clever and I tip my hat for thinking about using `unique-id()` for something actually useful. It’s very smart in this case.

    In the end, I think it’s sad we have to do things like that (or like the kind of code I featured on SitePoint lately to achieve cross-@media extend directives).
    It clearly shows how screwed Sass is when it comes to this stuff. As you said, both mixins and extends are half-solutions. Meanwhile, this is something that should be in the Sass core, not engineered by some crazy developers (a.k.a: us ;)).

  4. While this solution solves the problem you set out to solve, it feels over engineered.

    If your CSS was written in a BEM way, your CSS would already be leaner than your final output and you wouldn’t have all that overhead for Sass to compile (or others to come along and understand).

    Or maybe I’m just missing the point? Ha.

  5. Great work. Love how you have achieved this. I think this nicely bridges the gap between placeholder and mixins in a sensible way. I think even using BEM notation there is a call for using this even if it’s just for a clearfix and a few other minor elements. I found that with your example you could move the css to replace the @include button-static(false); and drop the else and the whole extend bit since the logic for creating the placeholder is independent of that. For me that’s easier to read unless there’s a specific reason for it being where it is? Not sure If I can link to things in my comments but I made a codepen of the stripped down mixins if it helps

  6. Great work Sam! I think examples like this should always be YMMV or your needs may vary. I would have never thought about using a dynamic placeholder to keep the declarations close to when the mixing was first used. Very neat idea and yet another example of the power of Sass and pushing our thinking using it. Lastly, a few thoughts:

    1) The !global when resetting the $Placeholder-Selectors map should be moot right? If you just call map-merge it edits the contents of that map right? For example this (https://github.com/Team-Sass/toolkit/blob/2.x.x/stylesheets/toolkit/_placeholders.scss#L9) need not reset the var.

    2) You use the word “properties” referring “declarations”. Is this correct? I like using the right words for things and just wanted to make sure.

  7. Good read!
    This is pretty highly engineered solution. While I kinda like it if feels a little bit over the top for me.
    I would just go for a “mixin” for my dynamic part + a “extend” of my static properties. I know this sort of goes into components “ambiguously defined” but I think there’s a win in simplicity and readability.

  8. Awesome technique! I feel like you’re taking a lot of heat for it because most people see it as overkill, but I think we all appreciate the elegance in your solution.

    I’m currently in the static extends camp for the same reasons as everyone else. Following up on your reply to Adam Moore’s question, I don’t see how making button-static into a placeholder class would require you to maintain it separately from the button mixin any more than you would already be maintaining as the mixin it currently is.

    Having said that, I’m currently charged with refactoring a large scale application’s stylesheets, and I can easily see a situation where the source position will matter. I’m using a combination of Atomic architecture with BEM syntax, so hopefully those situations are few and far between.

  9. Really interesting read. As some before me said, using BEM/SMACSS/OOCSS would make the output code leaner, on the other hand I don’t see why this technique can’t be used to create building blocks for modules.

    Apart from that, is it just me on Chromium 37 or am I missing some indentation on the code snippets?

  10. Thanks for your article highlighting these new features of Sass which I’ve yet to play with. I’ve learned the hard way that there’s beauty in simplicity and avoiding technical debt when other developers take over the project, so I’m interested to see how this pattern evolves to satisfy our desire for new features while remaining accessible.

  11. Wow, this is probably the coolest way I’ve seen to DRY out css, SO AWESOME!

    To the comments about just using better OOCSS and SMACCS, you’re right, to a degree. The problem with OOCSS and SMACCS that this solves is there is it removes ambiguity in what classes you need to style a certain object. This isn’t an issue if there’s only a handful of maintainers and you all are familiar with the css code base, and everyone knows to get your e-mail me button you need the classes .btn .btn-secondary .btn-emailme. Or in smaccs land that your layout to create the secondary element on the left nav is actually #leftNav .l-wide .l-morePadding or whatever it may be. Chaining css classes / id’s is difficult to maintain across larger teams, or teams where the backend / frontend is separated.

    OOCSS and SMACCS leave the components intentionally de-coupled so they can be skinned. The problem is the ties between all of your classes exist in documentation, in the dev minds, or in naming conventions. These are hard to keep up and maintain, so why not make your code maintain this for you?

    The approach above is also easy to explain once it’s implemented. You’re writing extensible classes for your css code. Sure someone will likely have to architect the core building blocks, but this is necessary in every other part of webdev. It’s moving towards making css manageable like any other language. Also, DRY CSS like this will eventually make a TDD approach to CSS possible.

  12. Very cool idea, but I’d have to agree with Hugo Giraudel on this being over-engineered. I especially don’t want to have to write a {mixin-name}-static mixin for every mixin I write.

    If you really want to write this kind of mixin, why not at least generalize it to a mixin called static(), and then you can use it inside any mixin like this:

    @mixin button($color) {
    @include static(‘button’) {
    border: 1px solid;
    border-radius: 5px;
    padding: .25em .5em;

    &:hover {
    cursor: pointer;
    }
    }

    background-color: $color;

    &:hover {
    background-color: mix(black, $color, 15%;
    }
    }

    Very simple to use now – all you need to do is stick any static properties inside the content of the static() mixin.

    Here’s an example using my generalized static() mixin: http://codepen.io/joelbyrd/pen/npiyo

  13. Agreed, Joel. Repeating the pattern for every mixin seems WET as well ;). I think Sam just provided this as an example to get the idea of static mixins across. Setting up the generic mixin can be simplified even further using toolkit btw.:

    @mixin static($mixin-name, $extend: true) {
      @if $extend == true {
        @include dynamic-extend($mixin-name) {
          @include static($mixin-name, false) {
            @content;
          };
        }
      }
      @else {
        @content;
      }
    }
    

    You can see that in action here: http://sassmeister.com/gist/e0c5ed4ab8e184b5f911

  14. I’ve been playing with SASS just for 4 or 5 months now and I’m no expert at all but as a designer’s point of view, this solution looks very fancy and nice and it shows the potential of sass functions BUT I find waaaaay easier to do a
    .btn{
    output static properties;
    &.primary{color properties}
    &.action{color properties}
    }
    . Of course then you’ll need to call for both classes on your DOM when targetting your button or anchor element but you are not duplicating properties as well as with Sam’s article. I understand the whole point of his approach is to call just one class and save computational time when making the call but… does it really change anything to improve 0.02s of load time?

    Again, I’m speaking from a designer’s point of view.

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

Nothing Fails Like Success

Our own @zeldman paints the complicated catch-22 that our free, democratized web has with our money-making capitalist roots. As creators, how do we untangle this web? #LetsFixThis