- From: Lea Verou via GitHub <noreply@w3.org>
- Date: Tue, 31 Mar 2026 23:50:35 +0000
- To: public-css-archive@w3.org
> It's of course possible. My point is that, without scoping, it's a nonsense rewrite. > > The point of macro hygiene is that you analyze what variables are _defined_ inside of a macro and detect what references are _to_ those definitions, and then use renaming to ensure they're safe and not accidentally clashing (while leaving other definitions alone). > > If we blindly rename all `var(--a)` refs, without using scoping to ensure that they are _actually_ referring to the `--a` arg/local, then we're not doing macro hygiene. Some of those refs are just going to be to unbound variables. My point was that they should not be able to reference *any other `--a` within the mixin*. Scoping or no scoping, having an `--a` in the mixin result that _sometimes_ refers to the mixin local argument and _sometimes_ to some other `--a` is *incredibly* confusing, and that's orthogonal to whether we scope or not. If you want to reference an outside `--a`, rename your argument. > I strongly disagree that "resolving _at all_ using _any_ sensible mechanism" is better. Authors are well-served by explainable, predictable behavior, with an easy-to-understand boundary around what's allowed. > > The current spec satisfies that: mixin contents are scoped, so your rules are restricted to what you can do inside of `@scope`. (Easy rule to apply, and matches an existing feature's restrictions.) As a result of this restriction, authors also get the behavior that all their `var(...)` refs that refer to args/locals get the same value, resolved against the applying element. (Simple to understand, straightforward beahvior.) > > Your proposal instead would have more complex behavior: > > * if you're styling the applying element or a descendant, it can use `var()` to refer to the arg/local, resolved against the applying element. > * if you're styling a different element, your `var()` _does not_ refer to the arg/local. Depending on the exact behavior being proposed, they'll either be invalid refs (making the property IACVT) or will resolve the literal arg/local value against a completely different element (potentially many different elements). Sure, all else being equal _predictable bad_ trumps _unpredictable bad_, but generally authors are well-served by behavior that does _what they want_ most of the time, and we do _frequently_ choose internal complexity to be able to conform to author expectations a larger % of the time. Predictability/explainability mostly comes into play when things _don't_ behave in the way they want. There is a ton of explainable, predictable behavior that produces poor user experiences because it's rarely what people want. It's not a pattern to emulate. If authors have written a sibling rule in their mixin, having the **entire rule be ignored** is _never, ever_ what they want. You can see this very clearly in the polls I linked. I'm happy to run any other poll you want and you'll see it there too. You would counter this with "but it's nonsensical", and yet to them it isn't. It's nonsensical to _your_ mental model because you have internalized that mixins = scoping rule. > The rules aren't dropped, they're scoped, exactly identical to being inside of `@scope`. That might be unexpected, sure, if you're brand new to the feature, but it's not difficult to predict and understand the effects. The author intent to using `@scope` is literally, _to scope_. If they include sibling or ancestor rules in there, that's _actually_ nonsensical. **The goal of `@mixin` is not to scope, it's to reuse a chunk of CSS.** Scoping, if there, is a weird side effect you can't avoid, not part of the intent. It _could_ be part of the intent if we had scoped and unscoped mixins, and the scoping was explicitly in the name. > > * Applying the same mixin multiple times to the same element (e.g. via different rules) would reuse the same mappings. Otherwise simple CSS rewrites such as combining rules or rewriting selectors can end up having unintended side effects. > > No, the rewriting is per-application. Executing the mixin injects rules into the stylesheet, with specific prop/vals in those rules. If you apply a mixin multiple times, those are separate invocations. (This matches how macro-ish things work in every language that has them.) I'm not as sure that it makes sense that applying the same mixin to the same element twice should use different properties. It certainly doesn't for built-in properties. In practice it probably won't matter though, and I don't think it should be a requirement that constrains our design space. It seems much easier if rewriting is scoped to the mixin and not to the application, since then you can rewrite any mixin with parameters to a mixin without parameters. Can you think of any use case where it makes a difference that matters? I've addressed whether precedent with other languages should be constraining above. CSS is very unique in its design, and while looking at prior art is useful, actual author experience is above philosophical purity. > > * Scoping would be shallow per mixin: if the mixin uses `@apply` to invoke another mixin, and the other mixin uses `--bar` as well, that's rewritten to a different `--bar`. If the parent mixin wants to use the same value, they can pass _their_ `--bar` as an argument. > > We do track "stack frames" - if the inner mixin defines its _own_ `--bar` arg/local, then yes, its `var(--bar)` refs are rewritten to _its_ `--bar` arg/local. If it doesn't have a `--bar` arg/local (and thus it's expecting to refer to the outside world's `--bar`, then it'll get caught by the outer mixin and rewritten to match.) I think that's problematic *regardless* of scoping. It means that variable references are unpredictable, and that matters if they are variables themselves. > > 1. Simply leave these properties undefined on these elements. At least declarations that don't use them still work, and authors can always provide fallbacks. > > 2. Resolve on the element AND on every other topmost root, then let inheritance handle the rest. > > 3. Resolve on the element, any top-most root, AND if one of these is an ancestor of the applying element, also resolve separately on every ancestor between the element and that top-most root. > > All of these mean that `var(--foo)` _sometimes_ refers to the `--foo` arg/local, and sometimes refers to a completely different value. That's what I've referred to as "confusing", and don't want to do. That's also the case for untyped arguments. 🤷🏽♀️ Any of these options produce exactly the same behavior for the element and its descendants as the current spec, but _vastly_ better behavior for elements outside that subtree. > > > 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. > > > > Rewriting at the stylesheet level with no concept of an applying element is not the actual author need though. I think. > > There's no other way to talk about this concept without going with something dramatically different and novel. If we want to use the concept of style rules and selectors, it has to be done at the stylesheet level; putting rules into the value space is the "something totally novel" I was referring to. I've outlined above how it could work without anything dramatic or novel. The core disagreement is whether dropping entire rules is better than allowing them and potentially having the `var()`s resolve to different values (whether that's undefined or just different). That's easily testable with authors, and I'd bet money almost nobody would pick to drop the entire rule no matter how you word it. > Rules don't get dropped. They get scoped and thus might not apply to any elements, same as putting that same selector in an [@scope](https://github.com/scope). The mental model is "just take the `@result`, replace arguments/locals, and substitute `@apply` with an [@Scoped](https://github.com/Scoped) (&)". That is an _extremely_ close desugaring (I'd have to think about what the actual corner-case differences might even be...), including for some of the less-expected corner behaviors! The distinction you're drawing between dropped vs not applying to anything sounds completely academic. My point is, **the `@scope` serves no obvious author goal**. It just feels like baggage, a wart you have to endure to use mixins. There are very valid cases to being able to target elements outside the applying element. > > Literally almost everybody in all polls I posted was **baffled** about the options where `& + p` was dropped (C & D). They found it _more_ surprising than any resolution mechanism whatsoever. > > Sure, people who aren't familiar with the internal mechanics of how this all needs to work can make assumptions that aren't actually possible to satisfy. That's the problem with making a social media poll that provides minimal context and just asks people for reactions to a context-less bit of new proposed syntax. Sometimes we can get useful information out of that, sometimes we can't. That's the whole point: people should not _need_ to be familiar with the internal mechanics! > > While none of the alternative resolution mechanisms is perfect, I would be surprised if authors found having entire rules ignored less surprising. Perhaps a simple rename could fix this (`@macro` → `@mixin`, `@mixin` → `@scoped-mixin` or something). > > I have no particular attachment to any naming. If your concerns end up getting totally resolved by just a rename along these lines, then (a) great! and also (b) I'm not really sure what your complaints were in the first place then. ^_^ That we need local arguments in the (current) `@macro` for this to be a viable option. Scoping is absolutely _not_ necessary for that, it's only necessary if everything resolving to the same value throughout is a hard constraint. -- GitHub Notification of comment by LeaVerou Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/13727#issuecomment-4166422590 using your GitHub account -- Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Tuesday, 31 March 2026 23:50:35 UTC