CSS Functions and Mixins Module

Editor’s Draft,

More details about this document
This version:
https://drafts.csswg.org/css-mixins/
Latest published version:
https://www.w3.org/TR/css-mixins-1/
Feedback:
CSSWG Issues Repository
Inline In Spec
Editors:
Miriam E. Suzanne (Invited Expert)
Tab Atkins-Bittner (Google)
Suggest an Edit for this Spec:
GitHub Editor

Abstract

This module provides the ability to define custom functional notations.

CSS is a language for describing the rendering of structured documents (such as HTML and XML) on screen, on paper, etc.

Status of this document

This is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-mixins” in the title, like this: “[css-mixins] …summary of comment…”. All issues and comments are archived. Alternately, feedback can be sent to the (archived) public mailing list www-style@w3.org.

This document is governed by the 03 November 2023 W3C Process Document.

1. Introduction

This section is not normative.

TODO

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.

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 (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-name> = <dashed-ident>
<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 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.

If the <css-type> of a function parameter, function dependency, or custom function return value can be described by a single <syntax-component>, then the type() function may be omitted:
@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(--a type(<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:

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># )

Mention semicolon upgrades.

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.

When a value is being computed, substitution of var(), env() and attr() must take place before <dashed-function> substitution.

Note: This means arguments passed to a custom function never contain var(), or similar functions.

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].

To substitute a dashed function in a value, with dashed function being a <dashed-function>:
  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. Let dependency values be an initially empty list.

  3. 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.

  4. Evaluate a custom function, using function, dashed function and dependency values.

  5. If failure was returned, return failure.

  6. 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

To evaluate a custom function, with function being a custom function, dashed function being the <dashed-function> invoking that function, and dependency values being a list of values.
  1. If the number of values in dashed function’s argument list is greater than the number of values in function’s parameters, return failure.

  2. 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.

  3. Let result be the resolved local value of the result descriptor, using function, dashed function, and dependency values.

  4. 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.

  5. If result is the guaranteed-invalid value, return failure.

  6. Otherwise, return result.

To resolve an argument, with value value, and parameter parameter:
  1. 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.

  2. 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.

  3. Return value.

To resolve a typed value, with value value, and syntax definition syntax:
  1. If value is the guaranteed-invalid value, return value.

  2. Compute value as if it were the value associated with a registered custom property whose syntax definition is syntax.

  3. If this would lead to a declaration being invalid at computed-value time, return the guaranteed-invalid value.

  4. 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.

To locally substitute a var() within a value, with function being a custom function, dashed function being the <dashed-function> invoking that function, and dependency values being a list of values:
  1. 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

    The guaranteed-invalid value.

  2. If substitution value is not the guaranteed-invalid value, replace the var() function by that value.

  3. Otherwise, if the var() function has a fallback value as its second argument, replace the var() function by the locally resolved fallback value.

  4. Otherwise, return failure.

A resolved local value is the value of a local variable or descriptor, except:

If any substitution algorithm returns failure, then the resolved local value of a local variable is the guaranteed-invalid value.

3.3. Cycles

TODO

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 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.

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: 16px;
  @media (width > 1000px) {
    result: 20px;
  }
}

The value of the 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 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);
}

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 CSSNestedDeclarations 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() {
  /* CSSNestedDeclarations { */
    --x: 42;
    result: var(--y);
  /* } */
  @media (width > 1000px) {
    /* ... */
  }
  /* CSSNestedDeclarations { */
    --y: var(--x);
  /* } */
}

Should we indeed use CSSNestedDeclarations for this purpose? The style attribute of the CSSNestedDeclarations rule should probably not be a regular CSSStyleDeclaration, since only custom properties and the result descriptor are relevant.

6. Appendix: The <syntax> Production

The <syntax> production represents a syntax definition, which may be used to impose a type on function parameters, function dependencies, or custom function return values.

<syntax-type> = angle | color | custom-ident | image | integer |
  length | length-percentage | number |
  percentage | resolution | string | time |
  url | transform-function
<syntax-combinator> = '|'
<syntax-multiplier> = [ '#' | '+' ]
<syntax-component-name> = '<' <syntax-type> '>' | <custom-ident>
<syntax-component> = <syntax-component-name> <syntax-multiplier>? |
  '<' transform-list '>'
<syntax> = '*' | <syntax-component> [ <syntax-combinator> <syntax-component> ]+

A <syntax-component> consists of either a <syntax-type>, which maps to one of the supported syntax component names; or a <custom-ident>, which represents any keyword. Additionally, a <syntax-component> may contain a multiplier, which indicates a list of values.

Note: This means that <length> and length are two different types: the former describes a <length>, whereas the latter describes a keyword length.

Multiple <syntax-component>s may be combined with a | <delim-token>, causing the syntax components to be matched against a value in the specified order.

<percentage> | <number> | auto

The above, when parsed as a <syntax>, would accept <percentage> values, <number> values, as well as the keyword auto.

red | <color>

The syntax definition resulting from the above <syntax>, when used as a grammar for parsing, would match an input red as an identifier, but would match an input blue as a <color>.

The * <delim-token> represents the universal syntax definition.

The <transform-list> production is a convenience form equivalent to <transform-function>+. Note that <transform-list> may not be followed by a <syntax-multiplier>.

Whitespace is not allowed between the angle bracket <delim-token>s (< >) and the <syntax-type> they enclose, nor is whitespace allowed to precede a <syntax-multiplier>.

Note: The whitespace restrictions also apply to <transform-list>.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Advisements are normative sections styled to evoke special attention and are set apart from other normative text with <strong class="advisement">, like this: UAs MUST provide an accessible alternative.

Tests

Tests relating to the content of this specification may be documented in “Tests” blocks like this one. Any such block is non-normative.


Conformance classes

Conformance to this specification is defined for three conformance classes:

style sheet
A CSS style sheet.
renderer
A UA that interprets the semantics of a style sheet and renders documents that use them.
authoring tool
A UA that writes a style sheet.

A style sheet is conformant to this specification if all of its statements that use syntax defined in this module are valid according to the generic CSS grammar and the individual grammars of each feature defined in this module.

A renderer is conformant to this specification if, in addition to interpreting the style sheet as defined by the appropriate specifications, it supports all the features defined by this specification by parsing them correctly and rendering the document accordingly. However, the inability of a UA to correctly render a document due to limitations of the device does not make the UA non-conformant. (For example, a UA is not required to render color on a monochrome monitor.)

An authoring tool is conformant to this specification if it writes style sheets that are syntactically correct according to the generic CSS grammar and the individual grammars of each feature in this module, and meet all other conformance requirements of style sheets as described in this module.

Partial implementations

So that authors can exploit the forward-compatible parsing rules to assign fallback values, CSS renderers must treat as invalid (and ignore as appropriate) any at-rules, properties, property values, keywords, and other syntactic constructs for which they have no usable level of support. In particular, user agents must not selectively ignore unsupported component values and honor supported values in a single multi-value property declaration: if any value is considered invalid (as unsupported values must be), CSS requires that the entire declaration be ignored.

Implementations of Unstable and Proprietary Features

To avoid clashes with future stable CSS features, the CSSWG recommends following best practices for the implementation of unstable features and proprietary extensions to CSS.

Non-experimental implementations

Once a specification reaches the Candidate Recommendation stage, non-experimental implementations are possible, and implementors should release an unprefixed implementation of any CR-level feature they can demonstrate to be correctly implemented according to spec.

To establish and maintain the interoperability of CSS across implementations, the CSS Working Group requests that non-experimental CSS renderers submit an implementation report (and, if necessary, the testcases used for that implementation report) to the W3C before releasing an unprefixed implementation of any CSS features. Testcases submitted to W3C are subject to review and correction by the CSS Working Group.

Further information on submitting testcases and implementation reports can be found from on the CSS Working Group’s website at http://www.w3.org/Style/CSS/Test/. Questions should be directed to the public-css-testsuite@w3.org mailing list.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-CONDITIONAL-3]
Chris Lilley; David Baron; Elika Etemad. CSS Conditional Rules Module Level 3. URL: https://drafts.csswg.org/css-conditional-3/
[CSS-ENV-1]
CSS Environment Variables Module Level 1. Editor's Draft. URL: https://drafts.csswg.org/css-env-1/
[CSS-IMAGES-4]
Tab Atkins Jr.; Elika Etemad; Lea Verou. CSS Images Module Level 4. URL: https://drafts.csswg.org/css-images-4/
[CSS-NESTING-1]
Tab Atkins Jr.; Adam Argyle. CSS Nesting Module. URL: https://drafts.csswg.org/css-nesting/
[CSS-PROPERTIES-VALUES-API-1]
Tab Atkins Jr.; Alan Stearns; Greg Whitworth. CSS Properties and Values API Level 1. URL: https://drafts.css-houdini.org/css-properties-values-api-1/
[CSS-SCOPING-1]
Tab Atkins Jr.; Elika Etemad. CSS Scoping Module Level 1. URL: https://drafts.csswg.org/css-scoping/
[CSS-SYNTAX-3]
Tab Atkins Jr.; Simon Sapin. CSS Syntax Module Level 3. URL: https://drafts.csswg.org/css-syntax/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. URL: https://drafts.csswg.org/css-values-4/
[CSS-VALUES-5]
Elika Etemad; Tab Atkins Jr.; Miriam Suzanne. CSS Values and Units Module Level 5. URL: https://drafts.csswg.org/css-values-5/
[CSS-VARIABLES]
Tab Atkins Jr.. CSS Custom Properties for Cascading Variables Module Level 1. URL: https://drafts.csswg.org/css-variables/
[CSS-VARIABLES-2]
CSS Custom Properties for Cascading Variables Module Level 2. Editor's Draft. URL: https://drafts.csswg.org/css-variables-2/
[CSSOM-1]
Daniel Glazman; Emilio Cobos Álvarez. CSS Object Model (CSSOM). URL: https://drafts.csswg.org/cssom/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[CSS-COLOR-5]
Chris Lilley; et al. CSS Color Module Level 5. URL: https://drafts.csswg.org/css-color-5/

Property Index

No properties defined.

@function Descriptors

Name Value Initial
result <declaration-value>? n/a (see prose)

IDL Index

[Exposed=Window]
interface CSSFunctionRule : CSSGroupingRule { };

Issues Index

TODO
Should duplicates be disallowed across parameters/dependencies as well?
Mention semicolon upgrades.
TODO
Should we indeed use CSSNestedDeclarations for this purpose? The style attribute of the CSSNestedDeclarations rule should probably not be a regular CSSStyleDeclaration, since only custom properties and the result descriptor are relevant.