- From: Tab Atkins Jr. via GitHub <noreply@w3.org>
- Date: Fri, 27 Mar 2026 17:51:13 +0000
- To: public-css-archive@w3.org
> and rules had to be restricted to descendants only so that arguments could be used everywhere and still inherit as expected. I'm exploring whether there could be an alternative way to do those things without these restrictions which [authors find extremely weird](https://front-end.social/@leaverou/116297811172593173). Slightly stronger than that. Scoping is needed to ensure that arguments can resolve once and inherit to children as a stable value, but *also* scoping is needed because those values need to resolve on *some element*, and if we don't scope the answer has to be "an element higher in the tree, with no obvious connection to the element using `@apply`". If one wants (a) element-relative values in `@apply` arguments to resolve based on the element that's applying (I think one definitely does), and (b) those resolved values to be usable inside the mixin without confusing restrictions (also something I think one wants), then the current design is forced unless we invent a totally novel way of handling arguments and element-relative values (which might not even be logically possible). (And remember that "element-relative" covers things like `em` units, sure, but also `sibling-index()`, `var()`, `attr()`, `if()` functions that query the element. Do you really want `@apply --foo(sibling-index());` to resolve that function unpredictably on whatever elements it happens to be applied to?) And as a benefit, the current design works exactly the same as setting typed custom properties manually, so it's a familiar model. > Arguments would be available everywhere anyway; inheritance is not needed for that. We introduced scoping only due to hygienic rewrites. Not entirely true. Without hygiene/scoping, arguments do... *something*. It's not clear what. There's a lot of possibilities and none of them look particularly close to what arguments do in other programming languages. The most straightforward *seems* to be that all arguments become untyped and unresolved, and we just substitute all captured `var()`s as part of resolving the `@apply`. But that means introducing a new type of variable substitution where it can leave `var()`/etc unresolved in some places and in some substitutions. > Yes, as long as we could have hygienically rewritten arguments, and I think something like the rewriting + injection mechanism I outlined above could be viable, with more work. If the mixin isn't scoped, then there's no sense in which hygiene exists. Hygienic renames, in any macro system, allow you to create variables *in* the mixin and refer to them, using the language's existing variable scoping rules, without having to worry about colliding with existing defined names or references. But the "variable scoping rules" of CSS *is* the ancestor/descendant relationship on the DOM tree. The only way for us to *use* that scope, at the time Mixins evaluate (aka stylesheet building time, long before we apply anything to any DOM), is to force the Mixin's result to be scoped. That way we *can* define a Mixin-internal variable at some point (the applying element) and know that it's what we're referring to at reference sites (the descendants, guaranteed due to scoping), and thus we can rely on a simple rename to avoid the collisions with outside custom properties and `var()`. Without that scoping, then we have *absolutely no idea* whether a given `var()` will actually be able to see the definition on the applying element. It *might* be a descendant and be okay, but it might be a sibling, a parent, or an arbitrary other element in the tree. Renaming the `var()` argument to match what how we rewrote the custom property on the applying element, then, is *potentially complete nonsense*, just an unknown custom prop that won't resolve to anything. The only way around *that* is to lift the variable definition to a spot where it's *guaranteed* to be visible to all `var()`s that might reference it, and that's the root element. But then we lose the ability to *resolve* the custom property against the applying element; it's now resolved on the root, if it's resolved *at all*. Say we bite that bullet and don't resolve things, removing argument types entirely (breaking from `@function` syntax); we decide that `@apply --foo(10em);` is okay to resolve to whatever with no reference to the applying element. But if we continue to use custom properties/`var()` *at all* to handle this stuff, then some arguments will *still* resolve early, because they're arbitrary-substitution functions and those *are* substituted even in untyped custom properties. So `@apply --foo(var(--size));` *will* resolve the `var()` in some way, on some element, whichever one happens to host the renamed custom property. The only way around *that* is to avoid using custom properties *at all*, meaning we're inventing something novel that has no connection to the DOM tree. At that point, using `var()` *at all* is pretty suspicious, because it doesn't act remotely like a custom property/`var()`. And that takes you all the way back over to `@macro` but with Sass-style interpolation, which runs entirely at the stylesheet level and can't access anything at all from the element. (That's where I'd probably like to *take* Macros eventually, but it's a much larger project, and in the meantime *it fails to do the many useful things that Mixins do with their current definition*.) ------- So. Those are the logical endpoints. Either we lean on the variable mechanism that exists today in CSS, which *requires* scoped rules and *implies* hygienic rewrites for convenience, or we invent something entirely novel (likely similar to Sass) which doesn't have a concept of the applying element at all and is just rewriting at the stylesheet level. Those two endpoints are what's *currently* in the spec, with `@mixin` and `@macro` (with a lot of headroom for `@macro` to develop in the future). There is no middle ground without doing something incoherent (at best, something that's *similar* to how custom props/var() works but *sometimes different*), as far as I can tell. That applies to all five of the suggestions in your OP, unfortunately - they're all trying to hit a middle ground and just doing something *similar* to custom properties but *sometimes different*, with the differences being somewhat unpredictable and not actually what authors would likely want or expect, as far as I can tell. -- GitHub Notification of comment by tabatkins Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/13727#issuecomment-4144269667 using your GitHub account -- Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Friday, 27 March 2026 17:51:14 UTC