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

Illustration:

Translations:

Share This:

Job Board

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

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?

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

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.

CSS

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

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

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

CSS

Maps

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.

Unique ID

Creating a basic mixin

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:

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:

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:

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

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.

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

$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.

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.

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:

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:

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

Also in Issue № 394

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.”

About the Author

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.

29 Reader Comments

More from ALA

Columns

The number of predictions that algorithms can make about us from even minimal data is shocking. Although we’re offered privacy settings that let us control who of our friends sees what, all our information and behavior tends to be fair game for behind-the-scenes tracking. We simply don’t know everything that’s being done with our data currently, and what companies might be able—and willing—to do with it in the future. Laura Kalbag believes it’s time to locate the exits.

Displays that are more tiny than our lowest-size breakpoints require a more condensed range of type sizes. If you don't already have in place a typographic system that can absorb the demands of this new context (watches, wearables, digital sticky notes, whatever), now might be the time to consider it. Matt Griffin was ready for anything because his site was simple and built to be future friendly.

March 19, 2015

From the Blog

It's all about us this week at ALA. From steps to sleep to social activities, we're counting every kind of personal data you can think of. But what's all that data add up to? How could we look at it—and ourselves—differently? In this week's On Our Radar, we ask ourselves—and our self—the tough questions.

That old monitor you've got lying around? Time to put it to work. Susan Robertson reminds us of how important it is to test our designs on older screens and ensure the things we build work well for everyone.

The future is here, for better or for worse—and this week, the ALA staff has been thinking about what that means: for our code (the impending arrival of HTTP/2), our content (publishing on Medium), and the cost of constant noise.

This week, the ALA staff is thinking about color accessibility, the process of building a vocabulary, the current state of web typography, and the lessons we can learn from skater culture. In other words: it's all about inclusion.