Skip to content

[css-mixins-1] Expose environmental variables inside apply's contents block #12631

@kizu

Description

@kizu

The mixin's @apply rule (https://drafts.csswg.org/css-mixins-1/#apply-rule) has an optional @contents block.

Mixin can also define scoped environmental variables (https://drafts.csswg.org/css-mixins-1/#scoped-env).

Proposal: expose the scoped environmental variables defined inside a mixin to be visible inside the contents block.

Example:

@mixin --nice-hover(@contents) {
	@env --nice-pink: deepPink;
	
	&:hover, &:focus-visible {
		@contents {
			color: env(--nice-pink);
		}
	}
}

.default-link {
	@apply --nice-hover;
}

.inverted-link {
	@apply --nice-hover {
		background: env(--nice-pink);
		color: contrast-color(env(--nice-pink));
	}
}

Exposing scoped environmental variables like this could allow to define mixins that can provide both some default rules, as well as a way to use parts of them alongside some built-in variables that were used as a part of the mixin.

If the @apply is used inside another mixin's definition that contains an environmental variable of the same name, the scoped environmental variable from the applied mixin will win over the local one.

@mixin --foo(@contents) {
	@env --test: lightgreen;
	@contents;
}

@mixin --bar() {
	@env --test: pink;
	@apply --foo {
	  background: env(--test); /* lightgreen */
	}
}

This is because it is possible to rename the local one to be used inside while we can't access the applied mixin to modify the inner one:

@mixin --bar() {
	@env --test: pink;
	@env --test-inner: env(--test);
	@apply --foo {
	  background: env(--test-inner); /* pink */
	}
}

Basically, the declarations inside the contents block first look at the scoped environmental defined inside the applied mixin, then look for them outside.

Open Questions

  • Should this be the default behavior for the scoped environmental variables, or should they not be exposed in this way by default and require us to explicitly provide them in some way?
    • I think it is better to do this by default: I don't see use cases that would require the opposite.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Friday Afternoon

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions