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

-

Published

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>
p {
  color: var(--theme-color);
}

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;
  }
}

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);
}

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

See the Pen by Benjamin Holmes (@bholmesdev) on CodePen.

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;
}

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;
}

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

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

.github-link {
  --color: #24292D;
}

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>

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

Here's another CodePen to see this working:

See the Pen by Benjamin Holmes (@bholmesdev) on CodePen.

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;
    }
}

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!

Thanks for reading!

This post is part of my hard-headed goal to ditch CSS preprocessors. I definitely miss nested styles... but the power of CSS variables, combined with literal microseconds of build time whenever I save, make me happy enough with raw CSS these days 😁

In case you're interested, I have another post taking a deeper dive into CSS variables, exploring how you can down on JS style manipulation. Check it out here!

Also, expect some more posts over the next few months. I finally graduated college, which I have... mixed feelings about to say the least. This month should be nothing but pumping out quarantine-ridden content before I start my first adult job.

So, join the newsletter below if this helped you! More to come soon.

The whiteboardist newsletter

Occasional posts and learnings from a lead Astro maintainer.