How Native Mixins Could Make Your CSS More Efficient

Although still conceptual, native CSS mixins could lead to cleaner CSS and further reduce the need for tools like Sass.

A fundamental concept of good programming is DRY, or “Don’t repeat yourself.” Put simply, DRY programming means that you should avoid having duplicate code sprinkled throughout your system or application. Duplicate code increases complexity, especially when it comes to maintenance. Should you update the code in one place, then you also need to hunt down every other instance and update them as well to ensure consistency and avoid bugs.

Contrasting that, DRY programming states that “every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” Of course, that’s easier said than done in the real world, especially when working on larger systems and applications. If you’ve written any amount of CSS over a period of time, then you’ve undoubtedly ended up with the same styles popping up time and again in your stylesheet.


The Problem of Duplicate CSS

I write these declarations time and again when I need to center elements, such as links in a menu:

display: flex;
justify-content: center;
align-items: center;

Or if I want to reset list styles, then I’ll often write this:

margin: 0;
padding: 0;
list-style: none;

In any case, the result is a lot of duplicate code floating around in my site’s stylesheet. There are several ways to address this, though. I can create helper classes for the above declarations, with names like .flex-center and .list0, and just add them to the relevant HTML elements. I can rewrite and optimize my CSS to abstract any duplicate styles. I can use a framework like Tailwind, and write HTML like this:

<div class="flex justify-center items-center></div>

<ul class="m-0 p-0 list-none"></ul>

I can even use a combination of all three. And if I’m using a tool like Sass to manage and compile my CSS, then I have a couple more options, specifically the @mixin and @extend rules. There are some subtle differences between how these two rules work, and each has their pros and cons, but essentially, they let you reuse the same bits of CSS throughout your stylesheet.

While these approaches can help you avoid the complexity of duplicate code, they unfortunately introduce other complexities. For instance, using helper classes and/or Tailwind can result in messy, complicated HTML. Meanwhile, using Sass means adding another tool to your workflow or build process that needs managing. Plus, the DRY-ness might only apply to your Sass code; the actual compiled CSS could be just as repetitive as it’d be sans Sass.

It sure would be nice, then, if CSS had its own native solution for this. The good news is that one might be on the way.


Introducing Native CSS Mixins (with Examples)

Once the sole domain of tools like Sass, features like custom properties and nested styles have made CSS more robust and efficient. And now there’s the possibility of CSS supporting native mixins.

To be clear, CSS mixins are still very much in the conceptual phase, with lots of debate and discussion surrounding them. It could be awhile (i.e., years) before browsers actually support them. Still, we can consider the possibilities right now — and they’re pretty exciting.

The aforelinked article by Miriam Suzanne goes into super-exhaustive detail about native CSS mixins. But here’s a simple example of what a CSS mixin could look like, using one of my earlier examples of non-DRY code:

@mixin --flex-center {
    display: flex;
    justify-content: center;
    align-items: center;
}

.menu {
    @apply --flex-center;
}

The beauty of this approach is that everything is done within CSS in a clear manner. I don’t need to sprinkle helper classes throughout my HTML, thus complicating my markup and blurring the lines between content and presentation, nor do I need to unnecessarily abstract my CSS or use a separate tool. Instead, I declare the --flex-center mixin, add the relevant declarations, and use the @apply rule to apply it to any selectors as needed.

CSS mixins could also include parameters for more customizable results. Consider this mixin for handling button styles, which can be very repetitive, especially in a large CSS framework:

@mixin --button-styles(
    --color-background,
    --color-hover,
    --color-active,
    --color-focus,
    --color-text
) {
    display: inline-flex;
    border-radius: 4px;
    background-color: var(--color-background);
    font-family: sans-serif;
    color: var(--color-text);

    &:hover {
        background-color: var(--color-hover);
    }

    &:active {
        background-color: var(--color-active);
    }

    &:focus {
        background-color: var(--color-focus);
    }
}

.btn-a {
    @apply --button-styles(red, blue, purple, green, white);
}

.btn-b {
    @apply --button-styles(yellow, pink, orange, brown, black);
}

The above CSS would be equivalent to this “normal” CSS:

.btn-a {
    display: inline-flex;
    border-radius: 4px;
    background-color: red;
    font-family: sans-serif;
    color: white;
}
.btn-a:hover { background-color: blue; }
.btn-a:active { background-color: purple; }
.btn-a:focus { background-color: green; }

.btn-b {
    display: inline-flex;
    border-radius: 4px;
    background-color: yellow;
    font-family: sans-serif;
    color: black;
}
.btn-b:hover { background-color: pink; }
.btn-b:active { background-color: orange; }
.btn-b:focus { background-color: brown; }

By moving duplicate declarations, like border-radius and font-family, into the --button-styles mixin, I can make my CSS DRY-er and more consolidated. This also means that if I want to make a global change to my buttons, like adding a transition for the background color, I can simply add it to the mixin and voila: all of my buttons now have it.

Using the above parameterized approach, I can expand the functionality of my earlier --flex-center mixin by making it more generic and flexible:

@mixin --flex-item(
    --justify-content,
    --align-items
) {
    display: flex;
    justify-content: var(--justify-content);
    align-items: var(--align-items);
}

.foo {
    @apply --flex-item(center, center);
}

.bar {
    @apply --flex-item(space-between, end);
}

The above CSS would be equivalent to this “normal” CSS:

.foo {
    display: flex;
    justify-content: center;
    align-items: center;
}

.bar {
    display: flex;
    justify-content: space-between;
    align-items: end;
}

Finally, Suzanne even makes the case for CSS mixins having conditional logic using the proposed @when and @else rules:

@mixin --card-styles(
    --color-background
) {
    background-color: var(--color-background);

	@when arg(--color-background: black) {
		color: white;
	} @else {
		color: black;
	}

}

.card-a {
    @apply --button-styles(white);
}

.card-b {
    @apply --button-styles(black);
}

The above CSS would be equivalent to this “normal” CSS:

.card-a {
    background-color: white;
    color: black;
}

.card-b {
    background-color: black;
    color: white;
}

This is a really simple example, but it’s easy to see how this technique could be used for elements like buttons and form inputs, which often have lots of contingent styles.

Using a mixin with parameters and conditional statements, you could set different styles based solely on whether a button’s style is “outline” or its shape is “pill” (sample code). Or for an <input> element, you could set different heights, padding values, and font sizes based on whether its size is “large,” “normal,” “small,” etc.

Currently, you’d probably create different classes for each of those variations, like .button-outline, .button-pill, .input-large, and .input-small, and apply them to the HTML elements that you want to style accordingly. In theory, however, mixins would allow most, if not all, of that to be handled entirely within your site’s stylesheet.


When CSS Mixins Go Bad

As cool as CSS mixins would be, there’ll certainly be the temptation to use them in unnecessary ways. That is, to write mixins that might be technically OK but don’t actually improve your CSS at all. Consider this example:

@mixin --text-bold {
    font-weight: 700;
}

.text {
    @apply --text-bold;
}

Syntactically, there’s nothing wrong with this mixin. But it doesn’t really make sense or add any benefit if all you want to do is bold some text.

For starters, if bolding the text is intended to emphasize it somehow, then that’s what the <strong> tag is for. (Hint: Always make your HTML as semantically rich as possible.) Otherwise, you’re better off just adding font-weight: 700; to your selectors. Writing single-purpose “atomic” mixins really just defeats the purpose of mixins in the first place.


The Tip of the Iceberg

The above examples are really just the tip of the iceberg. If they’ve piqued your curiosity, then I strongly recommend reading Suzanne’s original post for more detail on CSS mixins as well as native custom functions.

It might be awhile before we can actually use native CSS mixins. Even so, I still find it heartening that the feature’s even being considered. If nothing else, it’s proof that CSS is far from done. As was the case with custom properties, nesting, container queries, grid, and flexbox (to name a few), it’s great to see CSS continue to advance in ways that enable us web developers to work more efficiently and build even better websites.

Enjoy reading Opus? Want to support my writing? Become a subscriber for just $5/month or $50/year.
Subscribe Today
Return to the Opus homepage