[ad_1]
Most days I write vanilla CSS. Thanks to CSS variables and nesting, I have fewer reasons to resort to Sass or another preprocessor. The times I resort to Sass are usually when I have a @mixin
to iterate through a list of elements or to keep general styles DRY.
This could change for me in the not too distant future, as a new Designing the CSS function and mixin module was released at the end of June, after CSSWG decided to accept the proposal in February.
Note the name of the module: Functions And Mixins. There is a difference between the two.
This is all new and incredibly unbaked at the moment with a lot 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 will probably be some time before we have something real to work with and experiment with, but I like to try and get stuck into these things while they’re still in their early stages, knowing that things are bound to change.
In addition to the early draft of the specification, Miriam Suzanne published a detailed explanation that helps fill in some information gaps. Miriam is an editor on the spec, so I find anything she writes about it is useful context.
There is a lot to read! Here are my key findings…
Custom functions are extended custom properties
We are not talking about the single-purpose built-in features that we have come to appreciate in recent years – e.g. calc()
, min()
, max()
etc. Instead we talk about Custom functions defined with a @function
At rule that contains logic to return an expected value.
This makes custom functions very similar to a custom property. A custom property is just a placeholder for an expected value that we usually define in advance:
:root {
--primary-color: hsl(25 100% 50%);
}
Custom functions look quite similar, except that they are defined with @function
and take parameters. This is the syntax currently in the draft specification:
@function [( )]? {
result: ;
}
The result
is what the final value of the custom function yields. It’s a little confusing for me at the moment, but how I process this is that a custom function gives a custom PropertyHere is an example directly from the specification draft (slightly modified), which calculates the area of a circle:
@function --circle-area(--r) {
--r2: var(--r) * var(--r);
result: calc(pi * var(--r2));
}
Calling the function is similar to declaring a custom property, only without var()
and with arguments for the defined parameters:
.element {
inline-size: --circle-area(--r, 1.5rem); /* = ~7.065rem */
}
It seems that with current CSS features we can achieve the same thing as with a custom property:
:root {
--r: 1rem;
--r2: var(--r) * var(--r);
--circle-area: calc(pi * var(--r2));
}
.element {
inline-size: var(--circle-area, 1.5rem);
}
The reasons we prefer a custom function to a custom property are that (1) they can return one of several values at once 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 several values based on the viewport’s inline size.
/* 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));
}
}
Miriam continues: explain as a comma-separated parameter list like this requires additional CSSWG work, as it could be mistaken for a compound selector.
Mixins help maintain dry, reusable style blocks
Mixins feel more familiar to me than custom functions. That's what happens after years of writing Sass mixins, and that's perhaps the main reason I still use Sass from time to time.
Mixins look a bit like the new custom functions. Instead of @function
We work with @mixin
which is exactly how it works in Sass.
/* Custom function */
@function [( )]? {
result: ;
}
/* CSS/Sass mixin */
@mixin [( )]? {
}
So, custom functions and mixins are quite similar, but they are quite different:
- Functions are defined with
@function
; Mixins are defined with@mixin
but both are marked with a dash (eg--name
). - Features
result
in a value; mixins result in style rules.
This makes mixins ideal for abstracting styles that you might use as helper classes, such as a class for hidden text that is read by screen readers:
.sr-text {
position: absolute;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
As befits a utility, we can sprinkle this class on elements in the HTML to hide the text.
Skip to main content
Super handy! But as any Tailwind hater will tell you, this can result in ugly markup that is difficult to interpret when we focus on many Helper classes. There is no great danger with screen reader texts, but a short example from the Tailwind Documents should clarify this point:
It's really a matter of preference. But back to mixins! The deal is that we can use utility classes almost as little CSS snippets to create different style rules and maintain a clearer separation between markup and styles. If we use the same .sr-text
Styles from the past and mix them (yes, I coin that):
@mixin --sr-text {
position: absolute;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
Instead of jumping into HTML to apply the styles, we can embed them in other CSS style rules with a new @apply
at-rule:
header a:first-child {
@apply --sr-text;
/* Results in: */
position: absolute;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
A better example might be something that every project seems to need: center something!
@mixin --center-me {
display: grid;
place-items: center;
}
This can now be part of a larger set of rules:
header {
@apply --center-me;
/*
display: grid;
place-items: center;
*/
background-color: --c-blue-50;
color: --c-white;
/* etc. */
}
This is different from Sass, which @include
to call the mixin instead of @apply
We can even return larger blocks of styles, such as styles for the ::before
And ::after
Pseudonyms:
@mixin --center-me {
display: grid;
place-items: center;
position: relative;
&::after {
background-color: hsl(25 100% 50% / .25);
content: "";
height: 100%;
position: absolute;
width: 100%;
}
}
And of course, we saw that mixins accept argument parameters just like custom functions. You can use arguments when you want to relax the styles for variations, for example to define consistent gradients with different colors:
@mixin --gradient-linear(--color-1, --color-2, --angle) {
/* etc. */
}
We can specify the syntax for each parameter as a kind of type check:
@mixin --gradient-linear(
--color-1 type(color),
--color-2 type(color),
--angle type(angle),
) {
/* etc. */
}
We can further abstract these variables and set default values for them:
@mixin --gradient-linear(
--color-1 type(color),
--color-2 type(color),
--angle type(angle),
) {
--from: var(--color-1, orangered);
--to: var(--from-color, goldenrod);
--angle: var(--at-angle, to bottom right);
/* etc. */
}
…then we write the style rules of the mixin with the parameters as variable placeholders.
@mixin --gradient-linear(
--color-1 type(color),
--color-2 type(color),
--angle type(angle),
) {
--from: var(--color-1, orangered);
--to: var(--from-color, goldenrod);
--angle: var(--at-angle, to bottom right);
background: linear-gradient(var(--angle), var(--from), var(--to));
}
If you want, you can also add conditional logic here:
@mixin --gradient-linear(
--color-1 type(color),
--color-2 type(color),
--angle type(angle),
) {
--from: var(--color-1, orangered);
--to: var(--from-color, goldenrod);
--angle: var(--at-angle, to bottom right);
background: linear-gradient(var(--angle), var(--from), var(--to));
@media (prefers-contrast: more) {
background: color-mix(var(--from), black);
color: white;
}
}
This is all ready for @apply
the mixin in all desired rule sets:
header {
@apply --gradient-linear;
/* etc. */
}
.some-class {
@apply --gradient-linear;
/* etc. */
}
…and combine them with other mixins:
header {
@apply --gradient-linear;
@apply --center-me;
/* etc. */
}
This is all very high level. Miriam goes into the nuances of things like:
- Applying mixins at the root level (i.e. not in a selector)
- Working with container queries with the restriction that global custom properties must be set on an element other than the one being queried.
- The ability to set mixin parameters conditionally, for example with
@when
/@else
in the mixin. (Which makes me wonder about the newly proposed if()
function and whether instead of @when
.)
- Why we might draw the line at supporting loops, like Sass does. (CSS is a declarative language and loops are imperative flows.)
- Area-specific mixins (
@layer
? scope
? Something else?)
Miriam has a excellent presentation of open questions and discussions what's happening around mixins.
That's, um, it... at least for now.Man, that's a lot for my blonde brain to handle! Whenever I'm up to my neck in CSS spec drafts, I have to remind myself that the dust is still settling. The spec authors and editors are wrestling with many of the same questions we are—and more!—so it's not like a cursory glance at the drafts is going to make anyone an expert. And that's not even considering the fact that things can and probably will change by the time the whole thing becomes a recommended feature for browsers.
This will be an interesting area to observe, and you can do so with the following resources:
[ad_2]
Source link