Limited-Time Special Use Code to Get 30% Off Your First Buying EduBank. Find out more!
Most days, I’m writing vanilla CSS. Thanks to CSS variables and nesting, I have fewer reasons to reach for Sass or any other preprocessor. The times I reach for Sass tend to be when I need a @mixin to loop through a list of items or help keep common styles DRY.
That could change for me in the not-so-distant future since a new CSS Functions and Mixins Module draft was published in late June after the CSSWG resolved to adopt the proposal back in February.
Notice the module’s name: Functions and Mixins. There’s a distinction between the two.
This is all new and incredibly unbaked at the moment with plenty of TODO notes in the draft and points to consider in future drafts. The draft spec doesn’t even have a definition for mixins yet. It’ll likely be some time before we get something real to work and experiment with, but I like trying to wrap my mind around these sorts of things while they’re still in early days, knowing things are bound to change.
In addition to the early draft spec, Miriam Suzanne published a thorough explainer that helps plug some of the information gaps. Miriam’s an editor on the spec, so I find anything she writes about this to be useful context.
There’s a lot to read! Here are my key takeaways…
Custom functions are advanced custom properties We’re not talking about the single-purpose, built-in functions we’ve come to love in recent years — e.g., calc(), min(), max(), etc. Instead, we’re talking about custom functions defined with an @function at-rule that contains logic for returning an expected value.
That makes custom functions a lot like a custom property. A custom property is merely a placeholder for some expected value that we usually define up front:
:root {
--primary-color: hsl(25 100% 50%);
}
Custom functions look pretty similar, only they’re defined with @function and take parameters. This is the syntax currently in the draft spec:
@function <function-name> [( <parameter-list> )]? {
<function-rules>
result: <result>;
}
The result is what the ultimate value of the custom function evaluates to. It’s a little confusing to me at the moment, but how I’m processing this is that a custom function returns a custom property. Here’s an example straight from the spec draft (slightly modified) that calculates the area of a circle:
@function --circle-area(--r) {
--r2: var(--r) * var(--r);
result: calc(pi * var(--r2));
}
That said, the reasons we’d reach for a custom function over a custom property are that (1) they can return one of multiple values in a single stroke, and (2) they support conditional rules, such as @supports and @media to determine which value to return. Check out Miriam’s example of a custom function that returns one of multiple values based on the inline size of the viewport.
/* Function name */
@function --sizes(
/* Array of possible values */
--s type(length),
--m type(length),
--l type(length),
/* The returned value with a default */
) returns type(length) {
--min: 16px;
/* Conditional rules */
@media (inline-size < 20em) {
result: max(var(--min), var(--s, 1em));
}
@media (20em < inline-size < 50em) {
result: max(var(--min), var(--m, 1em + 0.5vw));
}
@media (50em < inline-size) {
result: max(var(--min), var(--l, 1.2em + 1vw));
}
}