For example, in ''random-item(auto, var(--foo), var(--bar))'',
the ''random-item()'' function selects between two random values,
either the result of ''var(--foo)'' or ''var(--bar)''.
This is true even if one of them contains commas, like:
.random-fonts {
--foo: Courier, monospace;
--bar: Arial, serif;
font-family: random-item(auto, var(--foo), var(--bar));
/* equivalent to: */
font-family: random-item(auto, {Courier, monospace}, {Arial, serif});
/* and thus, randomly, equivalent to either */
font-family: Courier, monospace;
/* or */
font-family: Arial, serif;
}
This behavior ensures that authors don't have to defensively wrap
any arguments containing [=arbitrary substitution functions=]
in ''{}'' characters;
what you see is what you get.
Note: This is different from the behavior of [=arbitrary substitution functions=]
substituted into "normal" functions or properties.
For example, ''--colors: red, blue, green; background: linear-gradient(var(--colors));''
works in the expected fashion,
producing a gradient with three color stops,
because normal functions don't do this separate [=argument grammar=] parse.
This behavior can be worked around
by immediately preceding an [=arbitrary substitution function=]
with the
For example, the following
will not work:
.invalid-if {
--if-clause: media(width >= 600px): blue;
color: if(var(--if-clause); else: green;);
}
The ''if()'' function entirely fails to parse
according to its [=argument grammar=]:
there's no `:` character separating the test from the value in the first branch.
To get the desired behavior of "spreading" the variable
into the function's arguments,
use ''...''':
.valid-if {
--if-clause: media(width >= 600px): blue;
color: if(...var(--if-clause); else: green;);
}
The [=spread syntax=] is three distinct <>s with the value `"."`,
all of which must not contain any whitespace between them,
or between the group and the subsequent [=arbitrary substitution function=].
That is, ''...var(--foo)'' is a valid use of the [=spread syntax=],
but ''... var(--foo)'' is not,
nor is ''. . .var(--foo)''.
The latter usages will result in the [=arbitrary substitution function=]
being evaluated at the normal time,
after the [=argument grammar=] has been applied,
and the period characters being part of the function's value.
Note: The [=spread syntax=] is only used
within an [=arbitrary substitution function's=] value,
as it's only referenced by the [=substitution=] algorithm
when parsing an [=arbitrary substitution function=].
Using it outside of that,
such as in ''width: ...var(--sidebar-width);'',
is not recognized as an early invocation;
instead, the periods are just part of the property's value,
unrelated to the ''var()'' function,
and would in this case make the 'width' property invalid.
(This is similar to JavaScript,
where this syntax was borrowed from,
where `[1, ...arr, 5]` is valid,
but `var x = ...arr;` is not.)
To substitute early-invoked functions
in a sequence of [=component values=] |values|:
1. [=list/For each=] [=arbitrary substitution function=] |func| in |values|
(ordered via a depth-first pre-order traversal)
using the [=spread syntax=]
that is not nested in the contents of another [=arbitrary substitution function=]:
1. [=Substitute early-invoked functions=] in |func|'s contents,
and let |early result| be the result.
2. If |early result| contains the [=guaranteed-invalid value=],
replace |func| in |values| with the [=guaranteed-invalid value=]
and [=iteration/continue=].
3. [=CSS/Parse=] |early result| acccording to |func|'s [=argument grammar=].
If this returns failure,
replace |func| in values with the [=guaranteed-invalid value=]
and [=iteration/continue=];
otherwise,
let |arguments| be the result.
1. [=Replace an arbitrary substitution function=] for |func|,
given |arguments|,
as defined by that function.
Let |result| be the returned list of [=component values=].
2. If |result| contains the [=guaranteed-invalid value=],
replace |func| in |values| with the [=guaranteed-invalid value=].
Otherwise, replace |func| in |values| with |result|.
2. Return |values|.
Resolving in Properties
Unless otherwise specified,
[=arbitrary substitution functions=] can be used
in place of any part of any property's value
(including within other [=functional notations=]);
and are not valid in any other context.
ISSUE: Should any of these functions be valid in contexts outside of properties?
variable-external-font-face-01.html
variable-font-face-01.html
variable-font-face-02.html
For example, the following code incorrectly attempts to use a variable as a property name:
.foo {
--side: margin-top;
var(--side): 20px;
}
This is
not equivalent to setting ''margin-top: 20px;''.
Instead, the second declaration is simply thrown away as a syntax error
for having an invalid property name.
If a property value contains one or more [=arbitrary substitution functions=],
and those functions are themselves syntactically valid,
the entire value's grammar must be assumed to be valid at parse time.
variable-reference-18.html
variable-reference-19.html
variable-reference-30.html
[=Arbitrary substitution functions=] are [=substituted=]
during style [=computed value|computation=],
before any other value transformations or introspection can occur.
If a property,
after [=property replacement=],
does not match its declared grammar,
the declaration is [=invalid at computed-value time=].
Note: Since [=arbitrary substitution functions=] resolve at [=computed value=] time,
if the resulting value after substitution is invalid,
the property falls back (essentially) to ''unset'' behavior,
rather than falling back to an earlier value in the [=cascade=]
the way declarations invalid at parse time do.
See [[#invalid-substitution]].
variable-declaration-16.html
variable-declaration-17.html
variable-declaration-18.html
variable-declaration-19.html
variable-declaration-21.html
variable-transitions-transition-property-all-before-value.html
variable-transitions-value-before-transition-property-all.html
If a property value,
after [=property replacement=],
contains only a single [=CSS-wide keyword=]
(and possibly whitespace/comments),
its value is determined as if that keyword were its [=specified value=] all along.
whitespace-in-fallback-crash.html
wide-keyword-fallback-001.html
wide-keyword-fallback-002.html
For example,
the following usage is fine from a syntax standpoint,
but results in nonsense when the variable is substituted in:
:root { --looks-valid: 20px; }
p { background-color: var(--looks-valid); }
Since ''20px'' is an invalid value for 'background-color',
the property becomes [=invalid at computed-value time=],
and instead resolves to ''transparent''
(the [=initial value=] for 'background-color').
If the property was one that's inherited by default,
such as 'color!!property',
it would compute to the inherited value
rather than the initial value.
While a ''var()'' function can't get a [=CSS-wide keyword=]
from the [=custom property=] itself--
if you tried to specify that,
like ''--foo: initial;'',
it would just trigger [[css-cascade-4#defaulting-keywords|explicit defaulting]]
for the custom property--
it can have a [=CSS-wide keyword=] in its fallback:
p { color: var(--does-not-exist, initial); }
In the above code, if the ''--does-not-exist'' property didn't exist
or is [=invalid at computed-value time=],
the ''var()'' will instead substitute in the ''initial'' keyword,
making the property behave as if it was originally ''color: initial''.
This will make it take on the document's initial 'color' value,
rather than defaulting to inheritance,
as it would if there were no fallback.
To replace substitution functions in a property |prop|:
1. [=Substitute arbitrary substitution functions=] in |prop|'s value,
given &bs<<;"property", |prop|'s name&bs>>; as the [=substitution context=].
Let |result| be the returned [=component value=] sequence.
2. If |result| contains the [=guaranteed-invalid value=],
|prop| is [=invalid at computed-value time=];
return.
3. [=CSS/Parse=] |result| according to |prop|'s grammar.
If this returns failure,
|prop| is [=invalid at computed-value time=];
return.
3. Otherwise, replace |prop|'s value with the parsed result.
css-variable-change-style-001.html
css-variable-change-style-002.html
variable-declaration-01.html
variable-declaration-02.html
variable-declaration-03.html
variable-declaration-04.html
variable-declaration-05.html
variable-generated-content-dynamic-001.html
variable-presentation-attribute.html
variable-reference-01.html
variable-reference-02.html
variable-reference-03.html
variable-reference-04.html
variable-reference-05.html
variable-reference-12.html
variable-reference-16.html
variable-reference-40.html
variable-reference-refresh.html
variable-substitution-background-properties.html
variable-substitution-basic.html
variable-substitution-filters.html
variable-substitution-replaced-size.html
variable-substitution-shadow-properties.html
variable-substitution-variable-declaration.html
variable-reference-cssom.html
Note that [=substitution=] takes place at the level of CSS tokens [[css-syntax-3]],
not at a textual level;
you can't build up a single token where part of it is provided by a variable:
.foo {
--gap: 20;
margin-top: var(--gap)px;
}
This is
not equivalent to setting ''margin-top: 20px;'' (a length).
Instead, it's equivalent to ''margin-top: 20 px;'' (a number followed by an ident),
which is simply an invalid value for the 'margin-top' property.
Note, though, that ''calc()'' can be used to validly achieve the same thing, like so:
.foo {
--gap: 20;
margin-top: calc(var(--gap) * 1px);
}
This also implies that the post-substitution value
might not be directly serializable as-is.
Here's a similar example to the preceding:
.foo {
--gap: 20;
--not-px-length: var(--gap)px;
}
The serialization of the computed (post-substitution)
value of ''--not-px-length''
is
not ''20px'',
because that would parse back as the single combined dimension;
instead, it will serialize with a comment between the two tokens,
like ''20/**/px'',
to enforce that they are separate tokens even when re-parsing.
variable-declaration-14.html
variable-declaration-53.html
variable-declaration-54.html
variable-declaration-55.html
variable-reference-15.html
variable-reference-without-whitespace.html
Invalid Substitution
When [=property replacement=] results in a property's value
containing the [=guaranteed-invalid value=],
this makes the declaration invalid at computed-value time.
When this happens,
the computed value is one of the following
depending on the property's type:
: The property is a non-registered [=custom property=]
: The property is a [=registered custom property=]
with [=universal syntax definition|universal syntax=]
:: The computed value is the guaranteed-invalid value.
: Otherwise
:: Either the property's inherited value
or its initial value
depending on whether the property is inherited or not, respectively,
as if the property's value had been specified as the ''unset'' keyword.
variables-substitute-guaranteed-invalid.html
For example, in the following code:
:root { --not-a-color: 20px; }
p { background-color: red; }
p { background-color: var(--not-a-color); }
the <p> elements will have transparent backgrounds
(the initial value for 'background-color'),
rather than red backgrounds.
The same would happen if the
custom property itself was unset,
or contained an invalid ''var()'' function.
Note the difference between this
and what happens if the author had just written ''background-color: 20px'' directly in their stylesheet -
that would be a normal syntax error,
which would cause the rule to be discarded,
so the ''background-color: red'' rule would be used instead.
Note: The invalid at computed-value time concept exists
because [=arbitrary substitution functions=] can't "fail early" like other syntax errors can,
so by the time the user agent realizes a property value is invalid,
it's already thrown away the other cascaded values.
Substitution in Shorthand Properties
[=Arbitrary substitution functions=] produce some complications
when parsing [=shorthand properties=] into their component longhands,
and when serializing [=shorthand properties=] from their component longhands.
If a [=shorthand property=] contains an [=arbitrary substitution function=] in its value,
the [=longhand properties=] it's associated with must instead be filled in
with a special, unobservable-to-authors pending-substitution value
that indicates the shorthand contains an [=arbitrary substitution function=],
and thus the longhand's value can't be determined until after [=substituted=].
This value must then be cascaded as normal,
and at computed-value time,
after [=substitution=],
the shorthand must be parsed
and the longhands must be given their appropriate values at that point.
variable-reference-36.html
variable-reference-37.html
variable-reference-38.html
variable-substitution-shorthands.html
vars-background-shorthand-001.html
vars-font-shorthand-001.html
Note: When a shorthand is written without an [=arbitrary substitution function=],
it is parsed and separated out into its component [=longhand properties=] at parse time;
the longhands then participate in the [=cascade=],
with the [=shorthand property=] more or less discarded.
When the shorthand contains a ''var()'', however,
this can't be done,
as the ''var()'' could be substituted with anything.
[=Pending-substitution values=] must be serialized as the empty string,
if an API allows them to be observed.
variable-definition-border-shorthand-serialize.html
vars-border-shorthand-serialize.html
----
[=Shorthand properties=] are serialized
by gathering the values of their component [=longhand properties=],
and synthesizing a value
that will parse into the same set of values.
If all of the component [=longhand properties=] for a given [=shorthand=]
are [=pending-substitution values=]
from the same original shorthand value,
the [=shorthand property=] must serialize to that original
([=arbitrary substitution function=]-containing)
value.
Otherwise,
if any of the component [=longhand properties=] for a given [=shorthand=]
are [=pending-substitution values=],
or contain [=arbitrary substitution functions=] of their own that have not yet been [=substituted=],
the [=shorthand property=] must serialize to the empty string.
Safely Handling Overly-Long Substitution
Naively implemented,
some [=arbitrary substitution functions=]
(such as ''var()'')
can be used in a variation of the "billion laughs attack":
.foo {
--prop1: lol;
--prop2: var(--prop1) var(--prop1);
--prop3: var(--prop2) var(--prop2);
--prop4: var(--prop3) var(--prop3);
/* etc */
}
In this short example, ''--prop4''’s computed value is ''lol lol lol lol lol lol lol lol'',
containing 8 copies of the original ''lol''.
Every additional level added to this doubles the number of identifiers;
extending it to a mere 30 levels,
the work of a few minutes by hand,
would make ''--prop30'' contain
nearly a billion instances of the identifier.
To avoid this sort of attack,
UAs must impose a UA-defined limit on the allowed length of the token stream
that an [=arbitrary substitution function=] expands into.
If an [=arbitrary substitution function=] would expand into a longer token stream than this limit,
it instead is replaced with the [=guaranteed-invalid value=].
long-variable-reference-crash.html
variable-exponential-blowup.html
This specification does not define what size limit should be imposed.
However, since there are valid use-cases for custom properties that contain a kilobyte or more of text,
it's recommended that the limit be set relatively high.
Note: The general principle that UAs are allowed to violate standards due to resource constraints
is still generally true here;
a UA might, separately, have limits on how long of a custom property they can support,
or how large of an identifier they can support.
This section calls out this attack specifically
because of its long history,
and the fact that it can be done without any of the pieces
seeming to be too large on first inspection.
Appendix B: Boolean Logic
In order to accommodate future extensions of CSS,
<> productions generally interpret their <> grammar branch as unknown,
and their boolean logic is resolved using 3-value Kleene logic.
In some cases (such as ''@supports''),
<> is instead defined as false;
in which case the logic devolves to standard boolean algebra.
3-value boolean logic is applied recursively
to a boolean condition |test| as follows:
* A leaf-level |test| resolves to
true, false, or unknown,
as defined by the relevant specification.
* ''not |test|'' evaluates to
true if its contained |test| is false,
false if it's true,
and unknown if it's unknown.
* Multiple |test|s connected with ''and'' evaluate to
true if all of those |test|s are true,
false if any of them are false,
and unknown otherwise (i.e. if at least one unknown, but no false).
* Multiple |test|s connected with ''or'' evaluate to
true if any of those |test|s are true,
false if all of them are false,
and unknown otherwise (i.e. at least one unknown, but no true).
If a “top-level” <> is unknown,
and the containing context doesn't otherwise define
how to handle unknown conditions,
it evaluates to false.
Note: That is, unknown doesn't “escape” a 3-value boolean expression
unless explicitly handled,
similar to how NaN
doesn't “escape” a [=top-level calculation=]).
Acknowledgments
Firstly, the editors would like to thank
all of the contributors to the previous level
of this module.
Secondly, we would like to acknowledge
Guillaume Lebas,
L. David Baron,
Mike Bremford,
Sebastian Zartner,
and especially Scott Kellum
for their ideas, comments, and suggestions for Level 5;
Changes
Recent Changes
Changes since the 11 November 2024 Working Draft:
* Dropped ''media-progess()'' and ''container-progress()'' in favor of using relevant units in ''progress()''.
(Issue 11826)
See also earlier changes.
Additions Since Level 4
Additions since CSS Values and Units Level 4:
* Added the “comma-wrapping” ''{}'' notation for function arguments.
* Defined several <>s for <> functions.
* Extended <> to handle [=flow-relative=] positions.
(Issue 549)
* Added the [[#progress|*-progress()]] family of functions, to represent interpolation progress between two values.
* Added the [[#mixing|*-mix()]] family of functions, to represent actually interpolating between two values.
* Added ''first-valid()'', to allow CSS's forward-compatible parsing behavior (drop invalid things, go with what's left) to be used with custom properties and other contexts where validity isn't known until after parsing.
* Added ''if()'' for inline conditionals.
* Added ''inherit()''.
* Added the ''toggle()'' and ''attr()'' functions.
* Added the ''random()'' and ''random-item()'' functions.
* Added the ''sibling-count()'' and ''sibling-index()'' functions.
* Added the ''calc-size()'' function, and the related 'interpolate-size' property.
* Added the <> syntax notation to the [=value definition syntax=].
Security Considerations
This specification allows CSS <> values to have various aspects of their request modified.
Although this is new to CSS,
every ability is already present in <{img}> or <{link}>, as well as via JavaScript.
The ''attr()'' function allows HTML attribute values
to be used in CSS values,
potentially exposing sensitive information
that was previously not accessible via CSS.
See [[#attr-security]].
Privacy Considerations
This specification defines units that expose the user's screen size
and default font size,
but both are trivially observable from JS,
so they do not constitute a new privacy risk.
Similarly the ''media-progress()'' notation exposes
information about the user's environment and preferences
that are already observiable via [=media queries=].
The ''attr()'' function allows HTML attribute values
to be used in CSS values,
potentially exposing sensitive information
that was previously not accessible via CSS.
See [[#attr-security]].