1. Introduction
This section is not normative.
2. Defining Custom Functions
A custom function can be thought of as an advanced custom property, which instead of being substituted by a single fixed value provides its substitution value based on function parameters and local variables.
Whenever a declaration’s value contains a reference to a custom function (a <dashed-function>), the value behaves as if it contained a var(), with the actual check against the property’s grammar delayed until computed-value time.
@function --negative ( --value) { result : calc ( -1 *var ( --value)); }
Then, that function can be referenced with
in some declaration (assuming --gap
is defined elsewhere):
html{ padding : --negative ( var ( --gap)); }
A custom function consists of a name (<function-name>), a list of parameters, a list of dependencies, a function body, and optionally a return type described by a syntax definition.
A function parameter consists of a name (<custom-property-name>); optionally a parameter type, described by a syntax definition; and optionally a default value.
A function dependency, is a special function parameter, that represents a local variable, function parameter, or custom property being implicitly passed as an argument from the calling context.
2.1. The @function Rule
The @function rule defines a custom function, and its syntax is:
<@function> = @function <function-name> <function-parameter-list>? ) [ using ( <function-dependency-list> ) ]? [ returns <css-type> ]? { <declaration-rule-list> } <function-parameter-list> = <function-parameter># <function-dependency-list> = <function-parameter># <function-parameter> = <custom-property-name> <css-type>? [ : <declaration-value> ]? <css-type> = <syntax-component> | <type()> <type()> = type( <syntax> )
The <function-name> production is a <function-token>, with the additional restriction that it must start with two dashes (U+002D HYPHEN-MINUS).
The name of the resulting custom function is given by the <function-name>, the function parameters are optionally given by <function-parameter-list>, the function dependencies are optionally given by <function-dependency-list>, and the return type is optionally given by the <css-type> following the "returns" keyword.
@function --foo ( --a <length>) { /* ... */ } @function --foo ( --a <color>) { /* ... */ } @function --foo ( --a <length>+) { /* ... */ }
However, any <syntax> that requires a <syntax-combinator> needs to be wrapped in the type() function:
@function --foo ( --atype ( <number> | <percentage>)) { /* ... */ }
Should duplicates be disallowed across parameters/dependencies as well?
If more than one @function exists for a given name, then the rule in the stronger cascade layer wins, and rules defined later win within the same layer.
The <function-name> of a @function rule is a tree-scoped name.
If the <function-parameter-list> contains the same <custom-property-name> more than once, or if the <function-dependency-list> contains the same <custom-property-name> more than once, then the @function rule is invalid.
The body of a @function rule accepts conditional group rules, such as @media. Additionally, it accepts the following descriptors:
-
The result descriptor, which determines the result of evaluating the function.
-
Custom properties, acting like local variable descriptors.
Unknown descriptors are invalid and ignored, but do not make the @function rule itself invalid.
2.2. The result Descriptor
Name: | result |
---|---|
For: | @function |
Value: | <declaration-value>? |
Initial: | n/a (see prose) |
The result descriptor determines the result of evaluating the custom function that is defined by a @function rule. Using locally substituted var() functions, it can reference function parameters, function dependencies, local variables, as well as other custom functions via <dashed-function>s.
The result descriptor itself does not have a type, but its resolved value is type checked during the substitution of a <dashed-function>.
3. Using Custom Functions
Similar to how the value of a custom property can be substituted into the value of another property with var(), the result of a custom function evaluation can be substituted with a <dashed-function>.
A <dashed-function> is a functional notation whose function name starts with two dashes (U+002D HYPHEN-MINUS). Its syntax is:
--*( <declaration-value># )
A <dashed-function> can only be used where var() is allowed.
If a property contains one or more <dashed-function>s, the entire property’s grammar must be assumed to be valid at parse time. At computed-value time, every <dashed-function> must be substituted before finally being checked against the property’s grammar.
A var() function within a local variable, or within the result descriptor, invokes local substitution, rather than the computed-value based substitution described in [css-variables].
-
Let function be the result of dereferencing the dashed function’s name as a tree-scoped reference. If no such name exists, return failure.
-
Let dependency values be an initially empty list.
-
For each dependency in function’s dependencies:
-
Let dependency value be the value that would be substituted if a var() function had been specified explicitly at the end of dashed function’s argument list, with dependency as its only argument.
-
If that substitution would have made a containing declaration invalid at computed-value time, set dependency value to the guaranteed-invalid value.
-
Append the result of resolving an argument to dependency values, using dependency value as value, and dependency as parameter.
-
-
Substitute any arbitrary substitution functions within dashed function’s arguments.
-
Evaluate a custom function, using function, dashed function and dependency values.
-
If failure was returned, return failure.
-
Otherwise, replace the <dashed-function> with the equivalent token sequence of the value resulting from the evaluation.
If substitute a dashed function fails, and the substitution is taking place on a property’s value, then the declaration containing the <dashed-function> becomes invalid at computed-value time.
3.1. Evaluating Custom Functions
-
If the number of values in dashed function’s argument list is greater than the number of values in function’s parameters, return failure.
-
For each value parameter in function’s parameters, let argument be the corresponding value in dashed function’s argument list at the same index:
-
If argument does not exist, set argument to the guaranteed-invalid value.
-
Replace the value in dashed function’s argument list with the result of resolving an argument, using argument as value, and parameter as parameter.
-
-
Let result be the resolved local value of the result descriptor, using function, dashed function, and dependency values.
-
If function has a return type, set result to the result of resolving a typed value, using result as the value, and the syntax definition associated with the return type as the syntax.
-
If result is the guaranteed-invalid value, return failure.
-
Otherwise, return result.
-
If value is not the guaranteed-invalid value, and parameter has a type, set value to the result of resolving a typed value using value as the value, and the syntax definition associated with parameter’s type as the syntax. This step may cause value to become guaranteed-invalid.
-
If value is the guaranteed-invalid value, and parameter has a default value, set value to one of the following:
- If parameter has a type
-
The result of resolving a typed value using the parameter’s default value as the value, and the syntax definition associated with parameter’s type as the syntax.
- Otherwise
-
The parameter’s default value.
-
Return value.
{}
:
@function --max-plus-x ( --list, --x) { result : calc ( max ( var ( --list)) +var ( --x)); } div{ width : --max-plus-x ({ 1 px , 7 px , 2 px }, 3 px ); /* 10px */ }
-
If value is the guaranteed-invalid value, return value.
-
Compute value as if it were the value associated with a registered custom property whose syntax definition is syntax.
-
If this would lead to a declaration being invalid at computed-value time, return the guaranteed-invalid value.
-
Otherwise, return that value.
3.2. Parameters and Locals
The function parameters and function dependencies of a custom function are available for local substitution as if they were declared as local variables at the start of the @function rule body.
Note: A local variable with the same name as a function parameter/function dependency is allowed, but will make the parameter/dependency unreachable for substitution
A local variable is a custom property defined with the body of a custom function. It is only visible within the function where it is defined.
-
Let substitution value be one of the following options, depending on the custom property named in the first argument of the var() function:
- If the custom property name matches a local variable within function
-
The resolved local value of that local variable.
- Otherwise, if the custom property name matches a parameter within function
-
The corresponding argument value within the dashed function.
- Otherwise, if the custom property name matches a dependency within function
-
The corresponding value of that dependency within dependency values.
- Otherwise
-
If substitution value is not the guaranteed-invalid value, replace the var() function by that value.
-
Otherwise, if the var() function has a fallback value as its second argument, replace the var() function by the locally resolved fallback value.
-
Otherwise, return failure.
A resolved local value is the value of a local variable or descriptor, except:
-
Any var() functions are replaced by local substitution.
-
Any <dashed-function>s are replaced by dashed function substitution.
-
Any arbitrary substitution functions other than var() and <dashed-function>s are substituted normally.
If any substitution algorithm returns failure, then the resolved local value of a local variable is the guaranteed-invalid value.
3.3. Cycles
The result descriptor and local variables within a custom function may reference other custom functions or custom properties, and may therefore create cycles.
For each element, add a node for every specified custom function to the graph described in CSS Variables 1 § 2.3 Resolving Dependency Cycles; add a node for each local variable defined within each of those functions; then, for each custom function func, add edges as follows:
-
From func to any custom function referenced by a <dashed-function> within func’s body.
-
From func to any custom property or local variable referenced by a var() within func’s body.
-
To func from any custom property or local variable that references func using a <dashed-function>.
A <dashed-function> referencing a custom function which is part of a cycle makes the containing declaration invalid at computed-value time.
Note: Cycles are disallowed even through branches that are not taken during execution.
--foo ()
is in a cycle with itself,
even though the media query never evaluates to "true":
@function --foo ( --x) { @media ( unknown-feature) { result : --foo ( 42 ); } result:1 ; }
Similarly,
is in a cycle with itself,
even though the local variable --x
is never referenced:
@function --bar () { --x : --bar (); result : 1 ; }
--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 : 10 px ; result : calc ( var ( --x) +var ( --y)); } div{ --x : --baz ( 1 px ); --y : --baz ( 2 px ); width : var ( --x); /* 11px */ height:var ( --y); /* 12px */ }
4. Execution Model of Custom Functions
Like the rest of CSS, custom functions adhere to a declarative model.
The local variable descriptors and 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 result descriptor of --mypi
is
.
@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.
4.1. 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 --suitable-font-size () { result : 16 px ; @media ( width >1000 px ) { result : 20 px ; } }
The value of the result descriptor
is
if the media query’s condition is true,
and
otherwise.
@function --suitable-font-size () { @media ( width >1000 px ) { result : 20 px ; } result:16 px ; }
The value of the result descriptor
is always
in the above example.
@function --suitable-font-size () { --size : 16 px ; @media ( width >1000 px ) { --size : 20 px ; } result:var ( --size); }
5. CSSOM
The CSSFunctionRule
interface represents a @function rule.
[Exposed =Window ]interface :
CSSFunctionRule CSSGroupingRule { };
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 >1000 px ) { /* ... */ } --y:var ( --x); }
The above will appear in the CSSOM as:
@function --bar () { /* CSSFunctionDeclarations { */ --x:42 ; result : var ( --y); /* } */ @media ( width >1000 px ) { /* ... */ } /* CSSFunctionDeclarations { */ --y:var ( --x); /* } */ }
5.1. The CSSFunctionDeclarations
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 ; };
style
attribute
must return a CSSFunctionDescriptors
object for the rule,
with the following properties:
- computed flag
-
Unset
- readonly flag
-
Unset
- declarations
-
The declared declarations in the rule, in specified order. This includes any local variables.
- parent CSS rule
- owner node
-
Null
The CSSFunctionDeclarations
rule, like CSSNestedDeclarations
, serializes as if its declaration block had been serialized directly.
6. Privacy Considerations
The constructs defined by this specification are defined and used entirely within CSS; they expose no new information.
7. Security Considerations
No issues have been opened against this specification.