Frontend Masters Boost RSS Feed https://frontendmasters.com/blog Helping Your Journey to Senior Developer Thu, 11 Dec 2025 22:37:01 +0000 en-US hourly 1 https://wordpress.org/?v=6.9 225069128 Thoughts on Native CSS Mixins https://frontendmasters.com/blog/thoughts-on-native-css-mixins/ https://frontendmasters.com/blog/thoughts-on-native-css-mixins/#respond Thu, 11 Dec 2025 22:29:27 +0000 https://frontendmasters.com/blog/?p=8020 I have some notes from various times I’ve thought about the idea of native CSS mixins so I figured I’d get ’em down on (digital) paper!

For the record, they don’t really exist yet, but Miriam Suzanne says:

The CSS Working Group has agreed to move forward with CSS-native mixins.

And there is a spec, but the spec only deals with @function (which does exist). Functions are a little similar but act only as a single value rather than a block of styles.

The idea comes from Sass @mixin.

We happen to use Sass (SCSS) at CodePen and as I write, we have 328 @mixin definitions in the codebase, so it’s clearly of use.

Here’s a practical-if-basic example:

@mixin cover {
  position: absolute;
  inset: 0;
}

In Sass, that doesn’t compile to anything. You have to use it. Like:

.modal-overlay {
  @include cover;
}

.card.disabled {
  &::before {
    @include cover;
    background: lch(0% 0 0 / 0.8);
  }
}

See how I’ve used it twice above. Compiled Sass will dump in the contents of the mixin in both places:

.modal-overlay {
  position: absolute;
  inset: 0;
}

.card.disabled {
  &::before {
    position: absolute;
    inset: 0;
    background: lch(0% 0 0 / 0.8);
  }
}

Things can get a little fancier in Sass, but it’s all pretty straightforward:

  • Mixins can include nesting and work in nested code. They can even slot in nested content you pass to it.
  • Mixins can use other mixins.
  • Mixins can have parameters (like a function) and use/calculate off those values in the output.

I would assume and hope that all of this is supported in native CSS mixins. The native version, as explained so far on Miriam’s site (which will almost definitley change!), the only difference is the usage syntax:

@mixin --cover {
  position: absolute;
  inset: 0;
}
.modal-overlay {
  @apply --cover;
}

I imagine it’s @apply instead of @include literally because Sass uses @include and Sass would have a hard time “leaving it alone” when processing down to CSS.

Is there enough here for browsers/standards to actually do it?

The W3C CSS Working Group has already OK’d the idea of all this, so I assume it’s already been determined there is value to native CSS having this ability at all. But what are those reasons?

  • Not having to reach for a preprocessor tool like Sass. I don’t think this is enough of a reason all by itself for them, but personally, I do. This is a paved cowpath, as they say.
  • Preprocessor output has potentially a lot of duplicate code. This leads to bigger CSS files. Perhaps not a huge issue with gzip/brotli in play, but still, smaller files is almost always good.
  • Integration with --custom-properties. I would think the parameters could be custom properties and there could be custom properties used generally with the style block. Custom properties can change dynamically, causing re-evaluated styles, so mixins can become more powerful expressions of style based on a comparatively small custom property change.
  • Custom Properties can cascade and be different values at different points in the DOM, so mixins might also do different things at different points in the DOM. All this custom property stuff would be impossible in a preprocessor.
  • It’s a nicer API than faking it with @container style(). You can test a custom property with a style query and dump out styles in certain places now, but it doesn’t feel quite right.

I wonder what else tipped the scales toward the working group doing it.

Parameter handling seems tricky.

You can pass things to a mixin, which I think is pretty crucial to their value.

@mixin --setColors(--color) {
  color: var(--color);
  background-color: oklch(from var(--color) calc(l - 40%) c h / 0.9);
}

But things can get weird with params. Like what happens if you call setColors() with no params? Does it just fail and output nothing?

.card {
  @apply --setColors(); /* ??? */
}

It’s possible --color is set anyway at the cascade level it’s being used at, so maybe it has access to that and outputs anyway? I assume if --color is set at the same cascade level and the param is passed, the param value wins? How does !important factor in?

And what about typed params? And default values? Seems doable but quite verbose feeling, especially for CSS. Is it like…

@mixin --setColors(
  --color type(color): red
) {
  color: var(--color);
  background-color: oklch(from var(--color) calc(l - 40%) c h / 0.9);
}

Maybe like that? I’m not sure what the syntax limitations are. Or maybe we don’t need default values at all because the var() syntax supports fallbacks already?

Feels like it could open up a world of more third-party CSS usage.

Imagine CSS carousels. They are so cool. And they are quite a bit of CSS code. Perhaps their usage could be abstracted into a @mixin.

The jQuery days were something like this pseudo-code:

// <script src="/plugins/owl-carousel.js"></script>
$(".owl-carousel").owlCarousel({
  gap: 10, 
  navArrows: true,
  navDots: true
});

Which morphed into JavaScript components:

@import SlickCarousel from "slickcarousel";

<SlickCarousel
  gap="10"
  navArrows={true}
  navDots={true}
/>

Maybe that becomes:

@import "/node_modules/radcarousel/carousel.css";

.carousel {
  @apply --radCarousel(
    --gap: 10px,
    --navArrows: true,
    --navDots: true
  );
}

The jQuery version was DIY HTML and this would be too. You could call that SSR for free, kids.

What about “private” variables?

I sort of remember Miriam talking about this at CSS Day this past year. I think this was the issue:

@mixin --my-thing {
  --space: 1rem;
  gap: var(--space);
  margin: var(--space);
}

.card {
  @apply --my-thing;
  padding: var(--space); /* defined or not? */
}

The question is, does that --space custom property “leak out” when you apply the mixin and thus can be used there? It either 1) does 2) doesn’t 3) some explicit syntax is needed.

I can imagine it being useful to “leak” (return) them, so say you wanted that behavior by default, but the option to not do that. Maybe it needs to be like…

@mixin --my-thing {
  @private {
    --space: 1rem;
  }
  gap: var(--space);
  margin: var(--space);
}

Don’t hate it. Miriams post also mentions being more explicit about what is returned like using an @output block or privatizing custom properties with a !private flag.

What about source order?

What happens here?

@mixin --set-vars {
  --papaBear: 30px;
  --mamaBear: 20px;
  --babyBear: 10px;
}

.card {
  --papaBear: 50px;
  @apply --set-vars;
  margin: var(--papaBear);
}

What margin would get set here? 50px because it’s set right there? 30px because it’s being overridden by the mixin? What if you reversed the order of the first two lines? Will source order be the determining factor here?

Are Custom Idents required?

All the examples use the --my-mixin style naming, with the double-dashes in front, like custom properties have. This type of using is called a “custom ident” as far as I understand it. It’s what custom functions are required to use, and they share the same spec, so I would think it would be required for mixins too.

/* 🚫 */
@mixin doWork {
}

/* ✅ */
@mixin --doWork {
}

Is this just like the way forward for all custom named things forever in CSS? I think it’s required for anchor names too, but not container names? I wish it was consistent, but I like backwards compatibility better so I can live.

Wouldn’t it be better if it was required for keyframes, for example? Like if you saw this code below, is it obvious what the user-named word is and what other things are language syntax features?

.leaving {
  animation: slide 0.2s forwards;
}

It’s slide here, so you’d have to go find it:

@keyframes slide {
  to { translate: -200px 0; }
}

To me it would be much more clear if it was:

.leaving {
  animation: --slide 0.2s forwards;
}
@keyframes --slide {
  to { translate: -200px 0; }
}

Annnnnnnd there is nothing really stopping us from doing that so maybe we should. Or take it one step further and adopt an emoji naming structure.

Calling Multiple Mixins

Would it be like?

@apply --mixin-one, --mixin-two;

Maybe space-separated?

@apply --mixin-one --mixin-two;

Or that is weird? Maybe you just gotta do it individually?

@apply --mixin-one;
@apply --mixin-two;

Does it matter?

Functions + Mixins

It seems to make sense that a mixin could call a function…

@mixin --box {
  gap: --get-spacing(2);
  margin-trim: block;
  > * {
    padding: --get-spacing(4);
  }
}

But would it be forbidden the other way around, a function calling a mixin?

@function --get-spacing(--size) {
  @apply get-vars(); /* ??? */
  result: 
    if (
      style(--some-other-var: xxx): 3rem;
      style(--size: 2): 1rem;
      style(--size: 4): 2rem;
      else: 0.5rem;
    )
}

Or is that fine?

Infinite Loops

Is it possible this opens up infinite loop problems in calculated styles? I don’t know if this is an actual problem but it’s brain-bending to me.

@mixin --foo(--val) {
  --val: 2;
}

.parent {
  --val: 1;
  .thing {
    @apply --foo(--val);
    --val: if(
        style(--val: 1): 2;
        else: 1;
      );
  }
}

Like, when evaluating a .thing, --val is 1 because of inheritance, but then we apply a mixin which changes it to 2, then we reset it back to 1, but if it’s 1 shouldn’t it reevaluate to 2? I just don’t know.

Unmixing

Miriam asks can you un-mix a mixin? Which is a great question. It’s very worth thinking about, because if there ends up being an elegant way to do it, it makes native mixins even more powerful and a big feather in their cap above what any preprocessor can do. I don’t hate an @unapply at first thought.

Thoughts?

Are you stoked for native mixins? Against it? Worried?

]]>
https://frontendmasters.com/blog/thoughts-on-native-css-mixins/feed/ 0 8020
Style Queries are Almost Like Mixins (But Mixins Would Be Better) https://frontendmasters.com/blog/css-does-need-mixins/ https://frontendmasters.com/blog/css-does-need-mixins/#comments Fri, 12 Jul 2024 18:41:35 +0000 https://frontendmasters.com/blog/?p=3002 I was styling a menu thing the other day, and it had some decently nested selectors. Normally I’m a pretty big fan of putting a class right on the thing you want to style and keeping CSS selectors pretty “flat” in that they are just that class alone. But for menus and the semantic HTML within, a little nesting seemed reasonable. For instance this HTML:

<nav class="site-nav">
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#">Contact</a></li>
    <li><a href="#">About</a></li>
    <li><a href="#">History</a></li>
  </ul>
</nav>

Can lead to this kind of CSS:

.site-nav {
  > ul {
    > li {
      > a {
        &:hover, 
        &:focus {
        }
      }
    }
  }
}

I can see how that turns some people off, but honestly it doesn’t bother me that much. The structure is reliable here and I’d rather this setup in CSS than a class on every one of those links in the HTML.

If we get into sub-menu territory though, it gets gnarlier:

.site-nav {
  > ul {
    > li {
      > ul { /* sub menu */
        > li {
          > a {
            &:hover,
            &:focus {
              
            }
          }
        }
      }
    }
  }
}

I’m willing to admit this is probably a bit too far in nesting town 😬. Particularly because at each of those nested levels there will be a bunch of styling and it will become hard to reason about quite quickly.

It occurred to me that the (newfangled, not production-ready) CSS style queries might be able to jump in and help here, because they behave a bit like a mixin.

Mixin?

Yeah! That’s what Sass called the concept, anyway. A mixin allows us to name a block of styles, then call them as needed. So…

@mixin linkHovered {
  background: red;
  color: white; 
}

.site-nav {
  > ul {
    > li {
      > a {
        &:hover, 
        &:focus {
          @include linkHovered;
        }
      }
    }
  }
}

So now we’ve kind of flattened out the styles a bit. We have this re-usable chunk of styles that we can just call rather than nest the styles so deeply.

What I wanted to try here was using Style Queries (and not Sass), but unfortunately it’s not quite as clean as I’d like. After using Sass for so long, this is what I wanted to work:

/* Invalid! Doesn't select anything */
@container style(--linkHovered) {
  background: red;
}

.site-nav {
  > ul {
    > li {
      > a {
        &:hover, &:focus {
          --linkHovered: true;
        }
      }
    }
  }
}

But that’s a no-go. The @container style query either needs to be nested within the other styles so that it has an implied selector (which defeats the “flatten the styles” purpose) or it needs an explicit selector inside it. So it needs to be like this:

@container style(--hasLinkHovered) {
  a {
    background: red;
  }
}

.site-nav {
  > ul {
    > li {
      &:has(> a:hover, > a:focus) {
        --hasLinkHovered: true;
      }
    }
  }
}

That works (where supported):

I just don’t love it. You have to set the Custom Property higher up in the nesting because container styles can’t style the thing they query. Plus now the true selector is a combination of the nesting and what’s in the container style query which is an awful brainbuster to keep track of.

This is not to say that Style Queries aren’t useful. They totally are, and I’m sure we’ll uncover lots of cool use cases in the coming years. I’m just saying that shoehorning them to behave exactly like mixins isn’t great.

It sure would be nice if CSS got native mixins!

It would be yet another Sass feature making it’s way into the platform. In the case of mixins, it would be a great win, because the CSS would be more efficient than the way Sass had to express the mixin concept back in CSS. If you used a @mixin 10 times under different selectors, those styles blocks would be barfed out 10 duplicate times in the CSS. Perhaps not the worlds biggest deal thanks to file compression, but certainly not as efficient as the language itself just referring to a single block of styles.

]]>
https://frontendmasters.com/blog/css-does-need-mixins/feed/ 2 3002