- From: andruud via GitHub <noreply@w3.org>
- Date: Fri, 20 Mar 2026 14:04:35 +0000
- To: public-css-archive@w3.org
> <ydaniv> ntim: I think this issue is hard to reason about, right now trying to think about it and combinations and it's hard
Let's modernize the original example following the resolutions in https://github.com/w3c/csswg-drafts/issues/12927:
```css
@mixin --foo() {
--test: lightgreen;
@result {
@contents;
}
}
@mixin --bar() {
--test: pink;
@result {
@apply --foo() {
background: var(--test); /* Which --test is seen here? */
}
}
}
div {
@apply --bar();
}
```
What is the background color on `<div>` here? Does the `var(--test)` function "see" the `--test:lightgreen` declaration within the `--foo()` mixin?
To discuss this, it could be useful to first illustrate that a `var()` from a mixed-in rule looks up locals/arguments from their mixin `@apply` chain before trying custom properties on the element. Example:
```css
@mixin --foo() {
--a: lightgreen;
@result {
& {
background: var(--a); /* lightgreen (from current mixin) */
color: var(--b); /* pink (from higher up in the @apply-chain) */
accent-color: var(--c); /* yellow (from the element) */
}
}
}
@mixin --bar() {
--a: purple;
--b: pink;
@result {
@apply --foo();
}
}
div {
--a: gold;
--b: black;
--c: yellow;
@apply --bar();
}
```
Adding chained `@contents` to that example:
```css
@mixin --foo() {
--a: lightgreen;
@result {
& {
background: var(--a); /* lightgreen (from current mixin) */
color: var(--b); /* pink (from higher up in the @apply-chain) */
accent-color: var(--c); /* yellow (from the element) */
@contents;
}
}
}
@mixin --bar() {
--a: purple;
--b: pink;
@result {
@apply --foo() {
--color-from-bar: var(--a);
@contents;
}
}
}
div {
--a: gold;
--b: black;
--c: yellow;
@apply --bar() {
--color-from-element: var(--a);
}
}
```
The innermost mixed-in rule here expands to:
```css
& {
background: var(--a); /* lightgreen (from current mixin) */
color: var(--b); /* pink (from higher up in the @apply-chain) */
accent-color: var(--c); /* yellow (from the element) */
--color-from-bar: var(--a); /* ? */
--color-from-element: var(--a); /* ? */
}
```
What do `--color-from-bar` and `--color-from-element` become here? There are two seemingly equally valid takes on how this could work:
1. A `@contents` rule acts like an `@apply` against a (limited) anonymous mixin defined elsewhere. **Under this idea, we get a background color of `lightgreen` in the original example.** In the chained-contents example, `--color-from-bar` and `--color-from-element` both become `lightgreen`.
2. The contents block associated with an `@apply` acts line an argument passed to the mixin. Regular arguments holding `var()` (e.g. `@apply --m(var(--x))`) are not passed into the mixin without substitution (the `var(--x)` passed as an argument will not see any locals named `--x` inside that mixin), and therefore any `var()` in the special contents argument should have the same behavior. **Under this idea, we get a background color of `pink` in the original example.** In the chained-contents example, `--color-from-bar` becomes `purple`, and `--color-from-element` becomes `gold`.
If I interpret the IRC log correctly, we seemed to be leaning against (1) last time around:
> <ydaniv> TabAtkins: proposing for this behavior a contents block will see vars defined in the body of mixin,
> <ydaniv> ... but it will not see ones defiend in args
...
> <ydaniv> TabAtkins: I think we got what we need
> <ydaniv> ... folks think it's a good behavior, got approvals
I'm a bit surprised by that, since it means that you cannot "safely" use locals (or custom properties from the element) in the passed contents block against a mixin you don't control, since you risk your `var()` functions getting shadowed. I would expect, however, that we would want a _default_ behavior of (2), with an opt-in for the `@apply dark-colors` use-case: `@apply dark-colors using --fg, --bg { … }` and/or `@apply dark-colors using all { … }`. The discussion took place under a different design overall, so perhaps it's not valid anymore. In any case, I'm fine with (1) if the group prefers that.
Finally, a question:
> <ydaniv> ... but it will not see ones defiend in args
Does this mean we intended to treat locals and parameters differently in terms of `var()` lookup/visibility? If yes, what is the reasoning behind this choice?
--
GitHub Notification of comment by andruud
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/12631#issuecomment-4098177377 using your GitHub account
--
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config
Received on Friday, 20 March 2026 14:04:37 UTC