Title: CSS Functions and Mixins Module
Shortname: css-mixins
Level: 1
Status: ED
Work Status: Exploring
Group: CSSWG
ED: https://drafts.csswg.org/css-mixins/
TR: https://www.w3.org/TR/css-mixins-1/
Editor: Miriam E. Suzanne, Invited Expert, http://miriamsuzanne.com/contact, w3cid 117151
Editor: Tab Atkins-Bittner, Google, http://xanthir.com/contact/, w3cid 42199
Abstract: This module provides the ability to define custom functional notations.
Default Highlight: css
Ignored Terms: cssText
Introduction {#intro} ===================== This section is not normative. Note: At this time, this specification only defines [=custom functions=], which operate at the level of CSS values. It is expected that it will define "mixins" later, which are functions that operate at the style rule level. [=Custom properties=] give authors a lot of power to define useful, sometimes complex values in one place, and then re-use them across their stylesheet. They can vary across the document, or based on Media Queries or other conditionals, making them very flexible and responsive. However, their values are fixed at the point they're defined, unable to be changed except by fully overriding their previous definition: a ''--shadow: 2px 2px var(--shadow-color)'' declaration takes its ''--shadow-color'' value from the element it's declared on, and later changes to ''--shadow-color'' on descendant elements don't alter the value of ''--shadow'' for them; they continue to use the shadow color defined where ''--shadow'' was defined. This is a common source of confusion for authors making heavy use of composite variables like this. [=Custom functions=] allow authors the same power as [=custom properties=], but parameterized: they have the same flexibility and conditionality as a [=custom property=] definition, but take values from other custom properties (or explicitly as arguments) at the point of use. For example, instead of a ''--shadow'' [=custom property=], a ''--shadow()'' [=custom function=] could be defined instead, like: @function --shadow(--shadow-color <color> : inherit) { /* If --shadow-color argument isn't passed, or doesn't successfully parse as a <color>, try to use the --shadow-color *property* from the element instead */ /* var(--shadow-color) refers to the --shadow-color parameter, rather than a custom property, but can still use a fallback value as normal */ result: 2px 2px var(--shadow-color, black); } .foo { --shadow-color: blue; box-shadow: --shadow(); /* produces a blue shadow */ /* or just */ box-shadow: --shadow(blue); } Defining Custom Functions {#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, computes its substitution value based on [=function parameters=] and the value of [=custom properties=] at the point it's invoked. Rather than the ''var()'' syntax that [=custom properties=] use for substitution, [=custom functions=] are invoked by <> syntax, allowing additional values to be passed as arguments.
A simple [=custom function=] to negate a value can be defined as follows:
		@function --negative(--value) {
		  result: calc(-1 * var(--value));
		}
		
Then, that function can be referenced with ''--negative()'' in some declaration:
		html {
			--gap: 1em;
			padding: --negative(var(--gap));
			/* or by passing the value explicitly, like: */
			padding: --negative(1em);
		}
		
<>s are [=arbitrary substitution functions=], like ''var()''. Their presence in a property's value causes it to be assumed valid at parse time, and only evaluated and parsed at computed-value time, after [=arbitrary substitution=] has occurred. The @function Rule {#function-rule} ---------------------------------------------- The ''@function'' rule defines a custom function, and consists of a name, a list of [=function parameter|parameters=], a function body, and optionally a return type described by a [=syntax definition=]. Each function parameter consists of a name (<>); optionally a parameter type, described by a [=syntax definition=]; and optionally a default value.
<@function> = @function <> <>#? )
	[ returns <> ]?
{
	<>
}

<> = <> <>? [ : <> ]?
<> = <> | <>
<type()> = type( <> )

The Function 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 [=custom function=] is given by the name of the <>, the optional [=function parameters=] are given by the <> values (defaulting to an empty set), and the optional [=custom function/return type=] is given by the <> following the returns keyword (defaulting to ''type(*)'').
If the <> of a [=function parameter=] or [=custom function/return type=] can be described by a single <>, then the ''type()'' function can be omitted: @function --foo(--a <length>) { /* ... */ } @function --foo(--a <color>) { /* ... */ } @function --foo(--a <length>+) { /* ... */ } However, any <> that requires a <> needs to be wrapped in the ''type()'' function: @function --foo(--a type(<number> | <percentage>)) { /* ... */ }
The name of a ''@function'' rule is a [=tree-scoped name=]. 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. If the [=function parameters=] contain the same <> more than once, then the ''@function'' rule is invalid.

The Function Body

The body of a ''@function'' rule accepts [=conditional group rules=], such as ''@media''. Additionally, it accepts the following descriptors: * The '@function/result' descriptor, which determines the result of [=evaluating a custom function|evaluating the function=]. If no '@function/result' descriptor exists, the function is valid, but always returns the [=guaranteed-invalid value=]. * [=Custom properties=], providing local variables. Unknown descriptors are invalid and ignored, but do not make the ''@function'' rule itself invalid. The '@function/result' Descriptor {#the-result-descriptor} ----------------------------------------------------------
Name: result
Value: <>?
For: @function
Initial: n/a (see prose)
The '@function/result' descriptor defines the result of [=evaluate a custom function|evaluating=] the [=custom function=] defined by its ''@function'' rule. Using ''var()'' functions, it can reference [=function parameters=], [=local variables=], as well as other [=custom functions=] via <>s. The '@function/result' descriptor itself does not have a type, but its [=resolve function styles|resolved=] value is type-checked during the [=substitute a dashed function|substitution=] of a <>. Arguments & Local Variables {#args} ----------------------------------- This section is non-normative. Within a [=custom function's=] [=function body=], the ''var()'' function can access [=local variables=] (the [=custom properties=] defined in the [=function body=]), [=function parameters=] (the values passed to the function, or set to default values), and [=custom properties=] defined at the call site (an element, or another [=custom function=]). In that list, earlier things "win" over later things of the same name-- if you have a [=local variable=] named '--foo', ''var(--foo)'' will be substituted by that [=local variable=], not by an argument or a custom property defined outside. The other values can still be accessed, however: setting the '--foo' local variable to ''initial'' will resolve it to the '--foo' parameter, while ''inherit'' will resolve it to the '--foo' custom property from the call site.
A [=custom function=] can access [=local variables=] and [=function parameters=] from functions higher up in the call stack: @function --outer(--outer-arg) { --outer-local: 2; result: --inner(); } @function --inner() returns <number> { result: calc(var(--outer-arg) + var(--outer-local)); } div { z-index: --outer(1); /* 3 */ } Similarly, [=custom properties=] are implicitly available: @function --double-z() returns <number> { result: calc(var(--z) * 2); } div { --z: 3; z-index: --double-z(); /* 6 */ } But [=function parameters=] "shadow" [=custom properties=], and [=local variables=] "shadow" both: @function --add-a-b-c(--b, --c) { --c: 300; result: calc(var(--a) + var(--b) + var(--c)); /* uses the --a from the call site's custom property, the --b from the function parameter, and the --c from the local variable */ } div { --a: 1; --b: 2; --c: 3; z-index: --add-a-b-c(20, 30); /* 321 */ }
Using Custom Functions {#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 into the value of a property with a <>. A <> is a [=functional notation=] whose function name starts with two dashes (U+002D HYPHEN-MINUS). Its syntax is:
	<dashed-function> = --*( <>#? )
A <> can only be used where ''var()'' is allowed. If a property contains one or more <>s, the entire property’s grammar must be assumed to be valid at parse time. At computed-value time, every <> must be [=substitute a dashed function|substituted=] before finally being checked against the property's grammar. Note: Within the body of a [=custom function=], ''var()'' functions might resolve differently than on the element the <> is used on. See [[#evaluating-custom-functions]]. A <> is evaluated in some context: either in a property value on an element (or in a descriptor that is eventually treated like a property on an element, such as in ''@keyframes''), or in a descriptor in the [=function body=] of another [=custom function=] that is being applied to a "hypothetical" element. Either way, this provides a calling context, which contains the property or descriptor name containing the <>, and the element (or "hypothetical" element) that property/descriptor is being applied to. As [=calling contexts=] are nested by <> evaluations inside of [=custom functions=], a [=calling context's=] root element is the real element at the root of the [=calling context=] stack.
To substitute a dashed function in a value, with |dashed function| being a <>: 1. Let |function| be the result of dereferencing the |dashed function|'s name as a [=tree-scoped reference=]. If no such name exists, return failure. 2. [=substitute arbitrary substitution functions|Substitute=] any [=arbitrary substitution functions=] within |dashed function|'s arguments, then parse it as ''<>#'' and let |arguments| be the result (a comma-separated list of CSS values). 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. 5. [=Evaluate a custom function=], using |function|, |arguments|, and |calling context|, and replace the <> 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 <> becomes [=invalid at computed-value time=].
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 */
	}
	
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. If the number of items in |arguments| is greater than the number of [=function parameters=] in |custom function|, return the [=guaranteed-invalid value=]. 2. Let |registrations| be an initially empty set of [=custom property registrations=]. 3. 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|. 4. 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|. 5. Let |argument rule| be an initially empty [=style rule=]. 6. 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|)''. 7. [=Resolve function styles=] using |argument styles|, |registrations|, and |calling context|. Let |argument styles| be the result. 8. Let |body rule| be the [=function body=] of |custom function|, as a [=style rule=]. 9. For each [=custom property registration=] of |registrations|, 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|. 10. [=Resolve function styles=] using |body rule|, |registrations|, and |calling context|. Let |body styles| be the result. 11. Return the value of the '@function/result' property in |body styles|.
To resolve function styles, given 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=] ''initial'' and ''inherit'' have their usual effect; all other [=CSS-wide keywords=] resolve 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)''. 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.
Cycles {#cycles} ---------------- The ''@function/result'' descriptor and [=local variables=] within a [=custom function=] may reference other [=custom functions=] or [=custom properties=], and may therefore create [[css-variables-1#cycles|cycles]]. For each element, add a node for every specified [=custom function=] to the graph described in [[css-variables-1#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 <> 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 <>. A <> 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.
In the following, --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, --bar() is in a cycle with itself, even though the local variable --x is never referenced:
	@function --bar() {
	  --x: --bar();
	  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 */
	}
	
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 --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);
	}
	
CSSOM {#cssom} ============== 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 "type(*)".
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 "type(*)" 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.