Re: [csswg-drafts] [css-mixins-1] Expose environmental variables inside apply's contents block (#12631)

> <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