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.
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:
- You have some CSS you want to reuse in a bunch of places
- 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:
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:
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:
- We turned our
color-link
mixin into a plain ole' CSS class - 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:
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!
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 ❤️