> evaluations
inside of [=custom functions=],
a [=calling context's=] root element
is the real element at the root of the [=calling context=] stack.
To replace a dashed function |dashed function|,
with a list of |arguments|:
1. Let |function| be the result of dereferencing
the |dashed function|'s name as a [=tree-scoped reference=].
If no such name exists, return the [=guaranteed-invalid value=].
2. For each |arg| in |arguments|,
[=substitute arbitrary substitution functions=] in |arg|,
and replace |arg| with the result.
Note: This may leave some (or all) arguments as the [=guaranteed-invalid value=],
triggering [=default values=] (if any).
3. If |dashed function| is being substituted into a property on an element,
let |calling context| be a [=calling context=]
with that element and that property
Otherwise, it's being substituted into a descriptor
on a "hypothetical element",
while evaluating another [=custom function=].
Let |calling context| be a [=calling context=]
with that "hypothetical element" and that descriptor.
4. [=Evaluate a custom function=],
using |function|, |arguments|, and |calling context|,
and return the [=equivalent token sequence=]
of the value resulting from the evaluation.
dashed-function-eval.html
dashed-function-standard-property.html
function-attr.html
function-shadow-animations.html
function-shadow-cache.html
function-shadow-container.html
function-shadow.html
A [=comma-containing productions|comma-containing value=]
may be passed as a single argument
by wrapping the value in curly braces,
{}:
@function --max-plus-x(--list, --x) {
result: calc(max(var(--list)) + var(--x));
}
div {
width: --max-plus-x({ 1px, 7px, 2px }, 3px); /* 10px */
}
In the following,
--foo() is in a cycle with itself:
@function --foo(--x) {
result: --foo(10);
}
Similarly,
--bar() is in a cycle with itself,
even though the local variable
--x is never referenced
by '@function/result':
@function --bar() {
--x: --bar();
result: 1;
}
However,
--baz() is not in a cycle with itself here,
since we never evaluate the
result declaration within
the
@media rule:
@function --baz(--x) {
@media (unknown-feature) {
result: --baz(42);
}
result: 1;
}
The function
--baz() is not in a cycle in the example below:
even though
var(--x) and
var(--y) appear in the function body,
they refer to a [=function parameter=] and [=local variable=], respectively.
The [=custom properties=]
--x and
--y
both reference
--baz(), but that's fine:
those [=custom properties=] are not referenced within
--baz().
@function --baz(--x) {
--y: 10px;
result: calc(var(--x) + var(--y));
}
div {
--x: --baz(1px);
--y: --baz(2px);
width: var(--x); /* 11px */
height: var(--y); /* 12px */
}
Evaluating Custom Functions {#evaluating-custom-functions}
----------------------------------------------------------
[=Custom functions=] are evaluated by, essentially,
pretending their function body is a [=style rule=]
being applied to a hypothetical element,
resolving styles as normal,
and then returning the value of the '@function/result' descriptor on that hypothetical element.
The hypothetical element "inherits" the values of all custom properties
as if it were a child of its [=calling context=],
with its [=function parameters=] overriding "inherited" custom properties of the same name.
To evaluate a custom function |custom function|,
given a [=calling context=] |calling context|
and a list of CSS values |arguments|,
returning a CSS value:
1. Let |substitution context| be a [=substitution context=]
containing &bs<<;"function", |custom function|&bs>>;.
Note: Due to [=tree-scoped names|tree-scoping=],
the same function name may appear multiple times on the stack
while referring to different [=custom functions=].
For this reason, the [=custom function=] itself is included
in the [=substitution context=], not just its name.
2. [=guarded|Guard=] |substitution context| for the remainder of this algorithm.
If |substitution context| is marked as [=cyclic substitution context|cyclic=],
return the [=guaranteed-invalid value=].
3. If the number of items in |arguments|
is greater than the number of [=function parameters=] in |custom function|,
return the [=guaranteed-invalid value=].
4. Let |registrations| be an initially empty set of [=custom property registrations=].
5. For each [=function parameter=] of |custom function|,
create a [=custom property registration=]
with the parameter's name,
a syntax of the [=parameter type=],
an inherit flag of "true",
and no initial value.
Add the registration to |registrations|.
6. If |custom function| has a [=custom function/return type=],
create a [=custom property registration=]
with the name "return"
(violating the usual rules for what a registration's name can be),
a syntax of the [=custom function/return type=],
an inherit flag of "false",
and no initial value.
Add the registration to |registrations|.
7. Let |argument rule| be an initially empty [=style rule=].
8. For each [=function parameter=] of |custom function|:
1. Let |arg value| be the value of the corresponding argument in |arguments|,
or the [=guaranteed-invalid value=] if there is no corresponding argument.
2. Let |default value| be the parameter's [=default value=].
3. Add a [=custom property=] to |argument rule|
with a name of the parameter's name,
and a value of ''first-valid(|arg value|, |default value|)''.
9. [=Resolve function styles=] using |custom function|, |argument rule|, |registrations|, and |calling context|.
Let |argument styles| be the result.
10. Let |body rule| be the [=function body=] of |custom function|,
as a [=style rule=].
11. For each [=custom property registration=] of |registrations|
except the registration with the name "result",
set its initial value
to the corresponding value in |argument styles|,
set its syntax
to the [=universal syntax definition=],
and prepend a [=custom property=] to |body rule|
with the property name and value in |argument styles|.
12. [=Resolve function styles=] using |custom function|, |body rule|, |registrations|, and |calling context|.
Let |body styles| be the result.
13. If |substitution context| is marked as a [=cyclic substitution context=],
return the [=guaranteed-invalid value=].
Note: Nested [=arbitrary substitution functions=]
may have marked |substitution context| as [=cyclic substitution context|cyclic=]
at some point after step 2,
for example when resolving '@function/result'.
14. Return the value of the '@function/result' property in |body styles|.
dashed-function-cycles.html
local-attr-substitution.html
local-if-substitution.html
To
resolve function styles,
given a [=custom function=] |custom function|,
a style rule |rule|,
a set of [=custom property registrations=] |registrations|,
and a [=calling context=] |calling context|,
returning a set of [=computed value|computed=] styles:
1. Create a "hypothetical element" |el|
that acts as a child of |calling context|'s element.
|el| is [=featureless=],
and only [=custom properties=]
and the '@function/result' descriptor apply to it.
2. Apply |rule| to |el| to the [=specified value=] stage,
with the following changes:
* Only the [=custom property registrations=] in |registrations| are visible;
all other [=custom properties=] are treated as unregistered.
* The [=inherited value=] of |calling context|'s property
is the [=guaranteed-invalid value=].
* On custom properties,
the [=CSS-wide keywords=] have the following effects:
- ''initial''
-
Resolves to the initial value of the custom property
within |registrations|.
- ''inherit''
-
Resolves like an ''inherit()'' function
with the custom property name as its one and only argument.
Note: This ensures that a [=function parameter=] defaulted to ''inherit''
is reinterpreted using the local [=parameter type=].
- any other [=CSS-wide keyword=]
-
Resolves to the [=guaranteed-invalid value=].
Note: ''initial'' references the [=custom property registration=]
created from the [=function parameters=],
letting you "reset" a property to the passed value.
''inherit'' inherits from the [=calling context=]'s element.\
On '@function/result',
all [=CSS-wide keywords=] are left unresolved.
Note: ''result: inherit'', for example,
will cause the <
> to evaluate to the ''inherit'' keyword,
similar to ''var(--unknown, inherit)''.
* For a given [=custom property=] |prop|,
during [=property replacement=] for that property,
the [=substitution context=] also includes |custom function|.
In other words, the [=substitution context=] is
&bs<<;"property", |prop|'s name, |custom function|&bs>>;
Note: Due to dynamic scoping,
the same property name may appear multiple times on the stack
while referring to different [=custom properties=].
For this reason, the [=custom function=] itself is included
in the [=substitution context=], not just its name.
3. Determine the [=computed value=] of all [=custom properties=]
and the '@function/result' "property" on |el|,
as defined in [[css-properties-values-api#calculation-of-computed-values]],
with changes from the previous step,
and the following:
* Aside from references to [=custom properties=]
(which use the values on |el| as normal)
and numbers/percentages
(which are left unresolved in custom properties, as normal),
all values which would normally refer to the element being styled
instead refer to |calling context|'s [=calling context/root element=].
Note: For example, ''attr()'' in a property,
or ''@container'' queries in the rule.
4. Return |el|'s styles.
Note: Only [=custom properties=] and the '@function/result' descriptor
will be used from these styles.
Execution Model of Custom Functions {#execution-model}
======================================================
Like the rest of CSS,
[=custom functions=] adhere to a declarative model.
The [=local variable=] descriptors
and '@function/result' descriptor
can appear in any order,
and may be provided multiple times.
If this happens, then declarations appearing later win over earlier ones.
@function --mypi() {
result: 3;
result: 3.14;
}
The value of the '@function/result' descriptor of
--mypi
is
3.14.
@function --circle-area(--r) {
result: calc(pi * var(--r2));
--r2: var(--r) * var(--r);
}
[=Local variable=] descriptors may appear before or after
they are referenced.
Conditional Rules {#conditional-rules}
--------------------------------------
A [=conditional group rule=] that appears within a ''@function''
becomes a [=nested group rule=],
with the additional restriction
that only descriptors allowed within ''@function''
are allowed within the [=nested group rule=].
[=Conditional group rules=] within ''@function''
are processed as normal,
acting as if the contents of the rule were present
at the [=conditional group rule=]'s location
when the condition is true,
or acting as if nothing exists at that location otherwise.
function-conditionals.html
function-container-dynamic.html
function-container-self.html
function-container-style.html
function-layer.html
function-media-dynamic.html
@function --suitable-font-size() {
result: 16px;
@media (width > 1000px) {
result: 20px;
}
}
The value of the '@function/result' descriptor
is
20px if the media query's condition is true,
and
16px otherwise.
Note that due to the execution model,
"early return" is not possible within a ''@function'':
@function --suitable-font-size() {
@media (width > 1000px) {
result: 20px;
}
result: 16px;
}
The value of the '@function/result' descriptor
is always
16px in the above example.
[=Local variables=] are also valid within conditional rules:
@function --suitable-font-size() {
--size: 16px;
@media (width > 1000px) {
--size: 20px;
}
result: var(--size);
}
Defining Mixins
A [=mixin=] is in many ways similar to a [=custom function=],
but rather than extending/upgrading [=custom properties=],
[=mixins=] extend/upgrade [=nested style rules=],
making them reusable and customizable with arguments.
mixin-basic.html
mixin-declarations.html
mixin-parsing.html
mixin-shadow-dom.html
For example, the following code sets up a mixin
applying all the properties you need for a "gradient text" effect,
including guarding it with [=supports queries=]:
@mixin --gradient-text(
--from type(<>): mediumvioletred,
--to type(<>): teal,
--angle: to bottom right,
) {
color: env(--from, env(--to));
@supports (background-clip: text) or (-webkit-background-clip: text) {
@env --gradient: linear-gradient(env(--angle), env(--from), env(--to));
background: env(--gradient, env(--from));
color: transparent;
-webkit-background-clip: text;
background-clip: text;
}
}
h1 {
@apply --gradient-text(pink, powderblue);
}
Note that this example also uses a [=scoped environment variable=]
(along with the arguments, which implicitly define [=scoped environment variables=])
which is scoped to
the rule itself
(rather than being applied to the element, like a [=custom property=] would be)
to hold a temporary value to aid in readability of the [=mixin=],
without polluting the element's styles with unwanted [=custom properties=].
This is exactly equivalent to writing a [=nested style rule=] literally into the `h1` styles:
h1 {
@nest {
@env --from: pink;
@env --to: powderblue;
@env --angle: to bottom right;
color: env(--from, env(--to));
@supports (background-clip: text) or (-webkit-background-clip: text) {
@env --gradient: linear-gradient(env(--angle), env(--from), env(--to));
background: env(--gradient, env(--from));
color: transparent;
-webkit-background-clip: text;
background-clip: text;
}
}
}
(Where
@nest is a fictitious rule
representing a [=nested declarations rule=].)
Issue: The entire ''@mixin'' feature is experimental and under active development,
and is much less stable than ''@function''.
Expect things to change frequently for now.
The @mixin rule
The ''@mixin'' rule defines a mixin,
and consists of a name,
a list of [=mixin parameters=],
and a [=mixin body=].
(Identical to ''@function'',
save that it lacks a [=custom function/return type=].)
<<@mixin>> = @mixin <> <>#? , @contents? )
{
<>
}
mixin-media-query-invalidation-2.html
mixin-media-query-invalidation.html
Differing from the ''@function'' rule,
the final item in the parameters list
can be the <> ''@contents'',
indicating that this [=mixin=] accepts a ''@contents'' block.
If a [=default value=] and a [=parameter type=] are both provided,
then the default value must parse successfully according to that parameter type’s syntax.
Otherwise, the ''@mixin'' rule is invalid.
A ''@mixin'' rule cannot be a [=nested group rule=];
it is invalid within the body of a [=style rule=].
The Mixin Preamble
The <> production
must start with two dashes (U+002D HYPHEN-MINUS),
similar to <>,
or else the definition is invalid.
The name of the resulting [=mixin=] is given by the name of the <>,
the optional [=mixin parameters=]
are given by the <> values
(defaulting to an empty set).
The name of a ''@mixin'' rule is a [=tree-scoped name=].
If more than one ''@mixin'' exists for a given name,
then the rule in the stronger cascade layer wins,
and rules defined later win within the same layer.
If the [=mixin parameters=]
contain the same <> more than once,
then the ''@mixin'' rule is invalid.
The Mixin Body
The body of a ''@mixin'' rule acts as a [=nested declarations rule=],
and accepts the same properties and rules
that a normal [=nested declarations rule=] would.
In particular, further [=mixins=] can be invoked
(via the ''@apply'' rule)
within a [=mixin body=].
Unknown properties and rules are invalid and ignored,
but do not make the ''@mixin'' rule itself invalid.
mixin-layers.html
Mixin Parameters
Within a [=mixin body=],
the ''env()'' function can access [=scoped environment variables=]
defined within the [=mixin body=],
defined by the mixin's arguments,
or those defined at the call site
(a style rule, or another [=mixin=]).
In that list, earlier things "win" over later things of the same name,
exactly as if the [=mixin body=] was a [=nested declarations rule=]
placed at its call site.
Specifically, it desugars to two [=nested declarations rules=],
to correctly reproduce the argument scope.
For example, the following mixin use:
@mixin --nested(--color2: green) {
@env --color3: blue;
background: linear-gradient(env(--color1), env(--color2), env(--color3));
}
p.nested {
@env --color1: red;
@apply --nested();
}
is exactly equivalent to:
p.nested {
@env --color1: red;
@nest {
@env --color2: green;
@nest {
@env --color3: blue;
background: linear-gradient(env(--color1), env(--color2), env(--color3));
}
}
}
(Where @nest here is a fictitous rule
representing a [=nested declarations rule=].)
[=Scoped environment variables=] defined in mixins can "shadow" ones defined higher up,
just like they can in [=nested style rules=] normally
(and like how variables can be shadowed in [=custom functions=]):
@mixin --z-index-a-b-c(--b, --c) {
@env --c: 300;
z-index: calc(env(--a) + env(--b) + env(--c));
/* uses the --a from the call site's envs,
the --b from the mixin parameter,
and the --c from the local env */
}
div {
@env --a: 1;
@env --b: 2;
@env --c: 3;
@apply --add-a-b-c(20, 30); /* 321 */
}
Note that [=mixin parameters=] are [=scoped environment variables=]
rather than [=custom properties=],
which means they exist in a separate namespace
and don't interfere with [=custom properties=].
The two generally act very similarly,
except for the slightly different definition syntax
(a [=custom property=] versus an ''@env'' rule)
and substitution functions
(''var()'' versus ''env()''),
but using [=custom properties=] actually writes those properties
into the invoking style rule
(potentially interacting with other definitions and uses of that [=custom property=],
which can be good or bad),
while ''@env'' rules in a [=mixin=]
aren't visible to anything outside of the [=mixin body=].
For example, this code shows a [=mixin=]
setting [=custom properties=] for other properties to use:
@mixin --tint-shade(--hue ) {
--light-tint: lch(90% 20% env(--hue));
--dark-shade: lch(10% 80% env(--hue));
}
.warning {
@apply --tint-shade(0deg);
background: var(--light-tint);
color: var(--dark-shade);
padding: .5em;
border: thick solid currentcolor;
}
If the [=mixin=] had instead declared them as ''@env --light-tint: ...;''/etc,
then those would only be available
within the [=mixin body=],
and the ''.warning'' rule would not be able to use them.
Note as well that [=scoped environment variables=] do not have an element context,
so they can't fully resolve values that require an element context,
like ''em'' lengths or ''var()'' functions,
at definition time.
Instead, they'll be resolved at each point they're used,
similar to an unregistered [=custom property=].
For example, this mixin applies a size to an element, its parent, and its children.
If an ''em'' length is used, all of these might be different values:
@mixin --triple-border(--size ) {
&, :has(&), & > * {
border-width: env(--size);
}
}
section {
font-size: 16px;
}
section > h1 {
font-size: 30px;
@apply --triple-border(.5em);
}
section > h1 > small {
font-size: 20px;
}
In this example,
despite them all being set with the same ''env(--size)'' value,
the `section` element has a border of ''8px'',
the `h1` element has a border of ''15px'',
and the `small` element has a border of ''10px''.
There are some ways to work around this, if desired.
For example, the value can be stored in a [=registered custom property=]
with the appropriate type,
so it resolves on one element and then inherits as the absolute value,
or passed through a [=custom function=] with a typed argument.
@function --as-length(--arg ) { result: var(--arg); }
@mixin --triple-border(--size ) {
:has(&) {
--triple-border-size: --as-length(env(--size));
}
&, :has(&), & > * {
border-width: var(--triple-border-size);
}
}
section {
font-size: 16px;
}
section > h1 {
font-size: 30px;
@apply --triple-border(.5em);
}
section > h1 > small {
font-size: 20px;
}
In the above code, the [=mixin parameter=]
is used to set a [=custom property=] on the parent element,
where it's forced to resolve as a length
due to being passed through the [=custom function=].
All the styles then use that [=custom property=]
(via ''var()'')
rather than the [=mixin parameter=]
(via ''env()''),
so they all share the same already-resolved value.
(Note that this value is resolved on the parent element
the [=custom property=] is defined on,
becoming ''8px'',
rather than resolving against the element the [=mixin=] is called on,
which would give ''15px''.
That's an unavoidable limitation of this situation.)
Issue: Do we want to try and figure out a way to have a mixin parameter
"remember" what element it was called on,
so it can resolve against that element consistently,
rather than require these workarounds?
I think that's inherently cyclic, tho--
imagine an ''em'' length used to set 'font-size' on a parent element.
The @contents Rule
In addition to accepting arguments passed by the <> in the ''@apply'' rule,
a [=mixin=] can accept a contents block.
This is indicated by the mixin using a final ''@contents'' <> in its parameter list,
and is passed by giving the ''@apply'' rule invoking the mixin
a block.
This allows the invoker of the [=mixin=] to pass an entire style block,
which the [=mixin=] can then substitute into itself.
This is useful, for example,
if the [=mixin=] handles some common conditions for the author,
and substitutes the [=contents block=] into a predefined ''@media'' or ''@container'' rule.
The syntax of a ''@contents'' at-rule is:
<<@contents>> = @contents [ { <> } ]?
contents-nested-declarations-fallback.html
contents-nested-declarations.html
contents-rule.html
That is, it is either an empty statement ended immediately by a semicolon,
or a block treated as a [=nested declarations rule=].
The empty statement form behaves identically to passing an empty block.
* If the [=mixin=] did not declare a ''@contents'' parameter,
the ''@contents'' rule is ignored,
substituting with nothing.
(And if the ''@apply'' rule tries to pass a [=contents blocks=],
it's an invalid invocation and has no effect.)
* Otherwise, if the ''@apply'' rule invoking the [=mixin=] passed a [=contents block=],
the ''@contents'' is replaced with the [=contents block=],
treating it as a [=nested declarations rule=].
* Otherwise, if the ''@apply'' rule did not pass a [=contents block=],
the ''@contents'' rule is replaced with its own <>,
treated as a [=nested declarations rule=].
Outside of a [=mixin body=],
the ''@contents'' rule is invalid and ignored.
For example, the following mixins
abstracts the cases that the page would consider to be appropriate
for a "single column" layout,
allowing the rest of the page to handle the case without worrying about the details,
so the conditions can be adjusted in the future if necessary:
@mixin --one-column(@contents) {
@media (width <= 800px) {
@contents;
}
}
@mixin --two-column(@contents) {
@media (width > 800px) {
@contents;
}
}
body {
@apply --one-column {
display: flex;
flex-flow: column;
}
@apply --two-column {
display: grid;
grid-template-columns: ;
}
}
Using Mixins {#using-mixins}
============================
The result of a [=mixin=] application
is substituted into the body of another [=style rule=]
as a [=nested declarations rule=]
via the ''@apply'' rule.
apply-nested-declarations.html
mixin-cross-stylesheet.html
mixin-from-import-with-media-queries.html
mixin-from-import.html
The @apply Rule
The ''@apply'' rule applies a [=mixin=],
causing it to substitute into the rule
in place of the ''@apply'' rule itself.
Its grammar is:
<<@apply>> = @apply [ <> | <> ] [ { <> } ]?;
apply-top-level.html
apply-within-mixin.html
For example, a [=mixin=] can be applied in any of these ways:
.foo {
@apply --one;
/* Invokes the --one mixin, with no arguments or contents. */
@apply --two(blue);
/* Invokes --two with one argument, and no contents. */
@apply --three {color: red;}
/* Invokes --three with no arguments, but with contents. */
@apply --four(blue) {color: red;}
/* Invokes --four with both an argument and contents. */
}
The ''@apply'' rule is only valid
in the body of a [=style rule=]
or [=nested group rule=];
using it in any other context causes it to be invalid and ignored.
''@apply'' rules are processed before any styles are applied,
as they effectively modify the stylesheet itself.
(Similar, in effect, to how [=conditional group rules=]
adjust which properties and rules are active in a stylesheet
before styles are applied.)
The ''@apply'' rule applies the [=mixin=]
named by the <> or the <>'s name.
If no such [=mixin=] exists,
the ''@apply'' does nothing.
If passed a <>,
the arguments passed to the <>
are mapped to the [=mixin's=] arguments;
if more arguments are passed than the length of the [=mixin's=] argument list,
the ''@apply'' application does nothing.
(Passing too few arguments is fine;
the missing arguments take their default values instead.)
A <> passes no arguments.
(That is, ''@apply --foo;'' is identical to ''@apply --foo();''.)
If the [=mixin=] declares a ''@contents'' parameter,
and the ''@apply'' rule has a <> block,
that block is passed as its [=contents block=].
If the [=mixin=] did not declare a ''@contents'' parameter,
having a <> block makes the ''@apply'' rule invalid
(similar to passing too many arguments).
Applying a mixin without arguments, or with an empty argument list,
is identical.
That is, these two invocations do exactly the same thing:
.foo {
@apply --no-args;
}
.bar {
@apply --no-args();
}
Passing a [=contents block=] is
not the same;
omitting the block entirely triggers fallback,
while passing an empty block will substitute the empty block:
@mixin --just-contents(@contents) {
@contents { color: red; }
}
.foo {
@apply --just-contents;
/* substitutes with `color: red;` */
}
.bar {
@apply --just-contents {};
/* substitutes with nothing at all */
}
Scoped Environment Variables {#scoped-env}
============================
Issue: This section should move to [[css-env-1]]
(or level 2, whatever).
The ''env()'' function,
defined in [[css-env-1]],
allows substituting the value of [=environment variables=]
into a stylesheet.
These are "global" variables,
defined by the [=user agent=],
rather than [=custom properties=]
defined by the page author on individual elements (and their descendants).
The ''@env'' rule allows defining scoped environment variables,
which are lexically scoped to a single rule in a stylesheet
(and any [=nested style rules=] within it).
Issue: A top-level ''@env'' probably needs to still be lexically scoped to just the stylesheet itself.
(After all, you can use `media=""` to link in a stylesheet
*effectively* auto-wrapped in an ''@media'',
and it would be weird to have that act differently
from actually using a wrapping ''@media''.)
That means only JS-defined custom envs are available cross-stylesheet.
The @env Rule
The ''@env'' rule defines a [=scoped environment variable=],
scoped to its parent rule
(and any other nested rule within its parent rule).
It's only valid within a [=nested style rule=]
or [=nested group rule=],
or at the "top level" of a stylesheet not nested in anything;
in any other context it's invalid and ignored.
Its grammar is:
<<@env>> = @env <> <>? : <>? ;
This defines a [=scoped environment variable=]
with a name given by the <>,
optionally a type given by <>,
and a value given by the <>?.
Its scope is the rule it's nested in,
or the stylesheet it's defined in
if it's not nested.
If a <> is given,
then when an ''env()'' referencing this [=scoped environment variable=]
is [=replace an arbitrary substitution function|replaced=],
the value must [=CSS/parse=] according to the <>,
or else the ''env()'' is replaced by the [=guaranteed-invalid value=].
If it successfully parses,
the value has the specified type.
Using Scoped Environment Variables
When an ''env()'' function is used
with a <> as the name of the environment variable,
it's potentially accessing a [=scoped environment variable=].
First the parent rule
of the property or rule the ''env()'' is used in
is checked to see if a [=scoped environment variable=] of that name
exists scoped to that rule.
If not,
its parent rule is checked,
recursively,
until finally the stylesheet itself is checked.
If that fails,
the global [=environment variables=] are finally checked.
Note: Custom global [=environment variables=] can be defined by the CSS.customEnv API.
(To be defined.)
Issue: Need some analogue to ''inherit'',
but for the parent lexical scopes.
Probably can't use ''inherit'' itself,
as that's a meaningful value that an ''env()'' could resolve to,
I guess?
Element-Dependent Values
A [=scoped environment variable=] can be defined
(both explicitly, and implicitly via [=mixin=] parameters)
that require knowledge about the element the style is being applied to,
but ''env()'' can be used in locations that don't have an element context.
For example, in this trivial example
an ''em'' value is passed to a mixin,
but the argument is then used in the ''@media'' rule inside the mixin.
@mixin --smaller(--size : 800px, @contents) {
@media (width <= env(--size)) {
@contents;
}
}
h1 {
@apply --smaller(50em) {
margin: 1em 0;
}
}
When this situation occurs,
if the value of the ''env()'' can be evaluated in its subsitution context at all,
it evaluates according to the normal rules for that context.
(For example, in a ''@media'', ''em'' values resolve against the initial font size,
rather than the font size of any particular element.)
If the value would be invalid in its substitution context,
it's instead replaced with the [=guaranteed-invalid value=].
(For example, ''var()'' isn't allowed in a ''@media''.)
CSSOM {#cssom}
==============
at-function-cssom.html
function-invalidation.html
The {{CSSFunctionRule}} Interface {#the-function-interface}
-----------------------------------------------------------
The {{CSSFunctionRule}} interface represents a ''@function'' rule.
[Exposed=Window]
interface CSSFunctionRule : CSSGroupingRule {
readonly attribute CSSOMString name;
sequence<FunctionParameter> getParameters();
readonly attribute CSSOMString returnType;
};
- name
-
The name of the [=custom function=].
- returnType
-
The [=custom function/return type=] of the [=custom function=],
represented as a [[css-properties-values-api-1#syntax-strings|syntax string]].
If the [=custom function=] has no return type,
returns
"*".
dictionary FunctionParameter {
required CSSOMString name;
required CSSOMString type;
CSSOMString? defaultValue;
};
- name
-
The name of the [=function parameter=].
- type
-
The [=parameter type|type=] of the [=function parameter=],
represented as a [[css-properties-values-api-1#syntax-strings|syntax string]],
or
"*" if the [=function parameter|parameter=] has no type.
- defaultValue
-
The [=default value=] of the [=function parameter=],
or `null` if the argument does not have a default.
While declarations may be specified directly within a ''@function'' rule,
they are not represented as such in the CSSOM.
Instead, consecutive segments of declarations
appear as if wrapped in {{CSSFunctionDeclarations}} rules.
Note: This also applies to the "leading" declarations in the ''@function'' rule,
i.e those that do not follow another nested rule.
@function --bar() {
--x: 42;
result: var(--y);
@media (width > 1000px) {
/* ... */
}
--y: var(--x);
}
The above will appear in the CSSOM as:
@function --bar() {
/* CSSFunctionDeclarations { */
--x: 42;
result: var(--y);
/* } */
@media (width > 1000px) {
/* ... */
}
/* CSSFunctionDeclarations { */
--y: var(--x);
/* } */
}
To
serialize a CSSFunctionRule,
return the concatenation of the following:
1. The string
"@function" followed by a single SPACE (U+0020).
2. The result of performing
serialize an identifier
on the name of the [=custom function=],
followed by a single LEFT PARENTHESIS (U+0028).
4. The result of [=serialize a function parameter=]
on each of the [=custom function's=] [=function parameter|parameters=],
all joined by
", "
(COMMA U+002C, followed by a single SPACE U+0020).
5. A single RIGHT PARENTHESIS (U+0029).
6. If the [=custom function=] has [=custom function/return type=],
and that [=custom function/return type=]
is not the [=universal syntax definition=] ("*"):
* A single SPACE (U+0020),
followed by the string
"returns",
followed by a single SPACE (U+0020).
* The result of performing [=serialize a CSS type=]
on that [=custom function/return type|type=],
followed by a single SPACE (U+0020).
7. A single LEFT CURLY BRACKET (U+007B),
followed by a SPACE (U+0020).
8. The result of performing [=serialize a CSS rule=]
on each rule in cssRules,
filtering out empty strings,
all joined by a single SPACE (U+0020).
Note: [=Serialize a CSS rule=] can return an empty string
when serializing an empty {{CSSFunctionDeclarations}} rule.
9. A single SPACE (U+0020),
followed by a single RIGHT CURLY BRACKET (U+007D).
To
serialize a function parameter,
return the concatenation of the following:
1. The result of performing
serialize an identifier
on the name of the [=function parameter=].
2. If the [=function parameter=] has a [=parameter type|type=],
and that [=parameter type|type=]
is not the [=universal syntax definition=]:
* A single SPACE (U+0020),
followed by the result of performing [=serialize a CSS type=]
on that [=parameter type|type=].
3. If the [=function parameter=] has a [=default value=]:
* A single COLON (U+003A),
followed by a single SPACE (U+0020),
followed by the result of performing [=serialize a CSS value=]
on that value.
To serialize a CSS type,
return the concatenation of the following:
1. If the <> consists of a single <>,
return the corresponding [[css-properties-values-api-1#syntax-strings|syntax string]].
2. Otherwise,
return the concatenation of the following:
* The string "type(",
i.e. "type"
followed by a single LEFT PARENTHESIS (U+0028).
* The corresponding [[css-properties-values-api-1#syntax-strings|syntax string]].
* The string ")",
i.e. a single RIGHT PARENTHESIS (U+0029).
The {{CSSFunctionDeclarations}} Interface {#the-function-declarations-interface}
--------------------------------------------------------------------------------
The {{CSSFunctionDeclarations}} interface represents a run
of consecutive [=declarations=] within a ''@function'' rule.
[Exposed=Window]
interface CSSFunctionDescriptors : CSSStyleDeclaration {
attribute [LegacyNullToEmptyString] CSSOMString result;
};
[Exposed=Window]
interface CSSFunctionDeclarations : CSSRule {
[SameObject, PutForwards=cssText] readonly attribute CSSFunctionDescriptors style;
};
The style attribute
must return a {{CSSFunctionDescriptors}} object for the rule,
with the following properties:
: [=CSSStyleDeclaration/computed flag=]
:: Unset
: [=CSSStyleDeclaration/readonly flag=]
:: Unset
: [=CSSStyleDeclaration/declarations=]
:: The declared declarations in the rule, in [=specified order=].
This includes any [=local variables=].
: [=CSSStyleDeclaration/parent CSS rule=]
:: [=this=]
: [=CSSStyleDeclaration/owner node=]
:: Null
The {{CSSFunctionDeclarations}} rule, like {{CSSNestedDeclarations}},
[=serialize a CSS rule|serializes=] as if its [=CSS declaration block|declaration block=]
had been [=serialize a CSS declaration block|serialized=] directly.
Privacy Considerations {#privacy}
===============================================
The constructs defined by this specification
are defined and used entirely within CSS;
they expose no new information.
Security Considerations {#security}
===============================================
No issues have been opened against this specification.
Changes {#changes}
===============================================
Changes since the First Public Working Draft of 15 May 2025:
* Added Web Platform Tests coverage