How you can (sort of) write SASS @mixins in plain CSS

Ben Holmes - May 8 '20 - - Dev Community

Trigger warning: I may use SASS and SCSS interchangeably throughout this post. You've been warned 😈

You probably clicked on this post thinking "wait, you can't write mixins in plain CSS. What kind of clickbait is this?"

Let me admit something right off the bat: this post does not show a one-to-one solution to replicate everything amazing about mixins. It's not like there's some magical corner of CSS to ENGAGE SASS MODE.

Plankton (from Spongebob) whipping a krabby patty into maximum overdrive

So yes, the pattern I'm about to detail isn't a direct translation of SASS / Stylus / LESS mixins. Still, it will help you overcome a problem that makes you reach for mixins in the first place:

  1. You have some CSS you want to reuse in a bunch of places
  2. You want to pass some parameters to that reusable CSS to adjust its behavior

Alright, expectations lowered. What do ya got?

Well, it all starts with our friend, CSS variables.

If you haven't used them before, CSS variables are a lot like SASS variables; you can store anything you want in them, and you can use them in any ruleset you define. However, these variables are extra powerful. Rather than being a value you define once and use everywhere, CSS variables can be reassigned at any level of the cascade. SASS details this distinction on their own documentation.

That kind of power let's you pull off something like this:

<div style="--theme-color: red">
  <p>I'm a red paragraph!</p>
</div>
<div style="--theme-color: blue">
  <p>I'm a blue paragraph!</p>
</div>
Enter fullscreen mode Exit fullscreen mode
p {
  color: var(--theme-color);
}
Enter fullscreen mode Exit fullscreen mode

Now, each paragraph will have a different color, depending on the variable value assigned by the parent div. Nice!

A concrete example

Say you have some social links on your site. You want all of these links to have a uniform layout, but you want to adjust the coloring to match the site you're linking to. In this case, we have two links to consider:

Demo of two links with different colors (Twitter and GitHub)

There's a bit more to this example than meets the eye. Notice each link has a different color in not one, but four places:

  • The text color
  • The SVG icon's fill
  • The link border
  • The background-color when you hover

Without variables, this would be extremely annoying / not-DRY. We'd have to write four rulesets (including nested styling for the icon) for every new color we add 💀

If you're cool enough to use a preprocessor, you could truncate your styles using a handy mixin. It goes a little something like this:

@mixin color-link($color) {
  color: $color;
  border: 1px solid $color;

  /* color the nested icon */
  svg {
    fill: $color;
  }

  /* change the background color on hover */
  &:hover {
    background-color: $color;
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, if we ever have a new link color to add, we can write a nice one-liner in a new selector:

.twitter-link {
  @include color-link(#1fa0f2);
}
Enter fullscreen mode Exit fullscreen mode

Here's a Pen to show this solution in action:

Alright, but plain CSS can't do that...

That's where you're wrong! Though CSS lacks our nifty @include syntax, we can still pass variables to a color-link ruleset in a similar way.

Let's start with a raw example, no variables applied:

/* change our mixin to a CSS class */
a.color-link {
  /* replace each $color reference with a hardcoded val for now */
  color: #1fa0f2;
  border: 1px solid #1fa0f2;
}

/* CSS can't do nested rulesets, so we gotta separate these out */
a.color-link > svg {
  fill: #1fa0f2;
}

a.color-link:hover {
  background-color: #1fa0f2;
  color: white;
}

a.color-link:hover > svg {
  fill: white;
}
Enter fullscreen mode Exit fullscreen mode

We did a couple things here:

  1. We turned our color-link mixin into a plain ole' CSS class
  2. We got rid of our nesting syntax since CSS still can't do that (but it could be coming soon!)

Now, we're ready to introduce some variable magic.

a.color-link {
  /* replace our hardcoded vals with a variable reference */
  color: var(--color);
  border: 1px solid var(--color);
}

a.color-link > svg {
  fill: var(--color);
}

a.color-link:hover {
  background-color: var(--color);
  color: white;
}

a.color-link:hover > svg {
  fill: white;
}
Enter fullscreen mode Exit fullscreen mode

And finally, we'll rewrite our twitter and github classes from before:

.twitter-link {
  --color: #1fa0f2;
}

.github-link {
  --color: #24292D;
}
Enter fullscreen mode Exit fullscreen mode

Boom. With CSS variables, we can just assign a value to our color variable in whatever CSS selector we choose. As long as we apply either of these guys alongside our color-link class...

<a class="color-link twitter-link">...</a>
Enter fullscreen mode Exit fullscreen mode

Our color-link "mixin" will apply the appropriate colors where we need them!

Here's another CodePen to see this working:

Yes, there are still limitations

This definitely makes your plain CSS stylesheets more DRY, but it fails to address some funkier use cases.

For example, SASS can pull off conditionals and looping inside its mixins. This can let you, say, pass true / false booleans as parameters to switch between styles.

/* arbitrary example, applying different layout mixins depending on browser support */
@mixin grid-if-supported($grid) {
    @if $grid {
        @include crazy-grid-layout;
    } @else {
        @include crazier-flexbox-layout;
    }
}
Enter fullscreen mode Exit fullscreen mode

Inspired by a rundown of magical mixin features found here

We also have to modify our HTML by applying more classes. Depending on who you ask, this defeats part of a mixin's sexiness. Mixins can be dropped into any existing ruleset you already have, without needing to create new classes or rulesets.

I agree with this somewhat, but this post should at least show off how powerful CSS variables really are. They can even store complicated styles with spacing and commas, like --crazy-padding: 12px 12px 0 0 or --dope-box-shadow: 1px 2px 3px #abcabc. The same can't be said for SASS mixin parameters!

Learn a little something?

Neato. In case you missed it, I launched an my "web wizardry" newsletter to explore more knowledge nuggets like this!

This thing tackles the "first principles" of web development. In other words, what are all the janky browser APIs, bent CSS rules, and semi-accessible HTML that make all our web projects tick? If you're looking to go beyond the framework, this one's for you dear web sorcerer 🔮

Subscribe away right here. I promise to always teach and never spam ❤️

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .