Educational guide to @function
@function: reusable formulas with CSS values
A custom property stores a value; a CSS function accepts arguments and computes a value at the call site. Learn to define its signature, resolve result, and keep an alternative when support is missing.
Mental model
Defining a function does not apply styles: call it inside a property value.
- Signature and arguments
--guide-space(--step <number>: 2) declares a parameter. --guide-space(1) passes an argument.
- Local resolution
var(--step) reads the parameter; local values and conditions determine result.
- Property value
The result replaces the call in padding. It must satisfy both returns and the padding grammar.
Function and parameter names start with -- and are case-sensitive. Calls use parentheses, not var(--function-name). Define @function outside style rules and use it where you would accept a CSS value; it can take no parameters.
Parameters, types, and default values
The signature describes the inputs and returns constrains the output value.
Separate parameters with commas. Types are optional: omitting one accepts type(*). A default follows a colon and must match its type. Here --step is a <number>, but the calculation produces a <length>. Read parameters with var(); calls accept values, not parameter names.
A spacing scale with an optional argument
--guide-space(): default 2
--guide-space(1): argument 1
Native CSS function: compare spacing with the expected result.
Base fallback: 1rem padding; call syntax is unavailable.
Omitting the argument uses 2; passing 1 overrides it. An incompatible argument can also trigger the default; without a default, a missing or incompatible required argument invalidates the call. <length> does not accept percentages: use <length-percentage> if you need both. type(<number> | <percentage>) allows a union, but your formula must still handle both types.
Scope: parameters, locals, and the element
A function is evaluated in the calling context, unlike a value fixed on :root.
var() checks local variables first, then parameters, then custom properties available at the call site. A local variable shadows a parameter with the same name; a parameter shadows the element property. Locals are not published as properties in the DOM.
Same function, different unit per component
--unit: 0.5rem; --step parameter: 2
--unit: 1rem; --step parameter: 2
Native CSS function: compare spacing with the expected result.
Base fallback: 1rem padding; call syntax is unavailable.
--computed-space only exists during evaluation. Avoid local names that accidentally shadow parameters, and pass explicit arguments when you want dependencies to be visible. Variable scope does not turn an @function definition into a selector-local rule; use distinctive names to avoid collisions between definitions.
result and conditional branches
The last applicable result wins; there is no early return as in JavaScript.
Place the general result first and the conditional one after it. In this example @media changes the value when the viewport is narrower than 700px. Moving the general result to the end overrides the branch even when its condition matches. You can also vary local variables and resolve a single result at the end.
Responsive spacing with two results
Viewport < 700px: 0.75rem; from 700px: 1.5rem
Native CSS function: compare spacing with the expected result.
Base fallback: 1rem padding; call syntax is unavailable.
The condition checks the viewport of the document running the function, not the card width. In the playground, the function lives inside an iframe: @media checks that iframe. @supports and other allowed conditional rules have their own compatibility; this example only demonstrates @media. if() is another way to choose a value and is not needed for this recipe.
Comparisons
Choose an abstraction based on what actually repeats.
Use a custom property for a token or a value configured through the cascade and inheritance. For a simple calculation, calc() with variables on the component is often enough. @function adds a reusable signature with arguments, types, defaults, and logic that can return different results for each call.
A simple custom property alternative
In this alternative, the formula is computed on the element receiving padding. If you define a composite value on :root and inherit it, its references resolve there: changing a dependency on a descendant does not automatically recompute that inherited value. A function computes at the call site. Neither option needs JavaScript.
A guarded fallback
Separate missing syntax from a function that is accepted but returns an invalid value.
All three previews define padding: 1rem outside the enhancement block. They only apply the call inside @supports (padding: --name()). In an engine without custom function call syntax, the condition fails and the base stays intact. This test checks syntax acceptance: it does not verify that the name exists, its arguments, or its result.
Counterexample: the earlier declaration does not always save you
Deliberately incorrect example, with no preview: if the engine accepts the call but the function does not exist, padding is invalid at computed-value time and uses its initial value, 0; it does not return to 1rem.
The guard does not rescue a broken call in a supporting browser either. Check the computed value and test real arguments and branches. var(--token, alternative) is not a try/catch for every error in a property. The examples test padding with @supports to avoid also requiring at-rule(@function) support.
Common mistakes
Diagnose the signature, resolution, and destination property separately.
The conditional branch never changes the result
- Expected result
- The value adapts when @media matches.
- Cause
- A later general result overrides the conditional one; result does not stop evaluation.
- Solution
- Put the base first and exceptions after it, or change locals and compute result at the end.
The function exists, but the property is not applied
- Expected result
- The result matches both its declared type and the receiving property.
- Cause
- A required argument, returns, or the destination grammar does not match; returning a length does not make it valid for color.
- Solution
- Check arguments/defaults, the output type, and computed value. Test a minimal call before adding conditions.
A variable returns another value or is missing outside
- Expected result
- Each name resolves in its intended scope.
- Cause
- A local or parameter shadows the element property, or you are trying to read a local from outside the function.
- Solution
- Rename variables to avoid collisions and return data through result. Do not expect locals to be inherited in the DOM.
The fallback disappears in a supporting browser
- Expected result
- The interface keeps a useful value with and without the enhancement.
- Cause
- The call passes @supports but fails at computed-value time: the browser does not restore the previous declaration.
- Solution
- Keep a base outside the guard and also verify the call, values, and branches in the engine that accepts it.
Support and compatibility
The overall status does not certify every signature, branch, or returned value.
@function has limited availability. On October 22, 2026, Web Platform DX lists Chrome and Edge support from 139, with no stable Firefox or Safari support. MDN BCD marks Safari as preview; that does not mean a stable release is available.
The Baseline card reports the overall feature. Each example notice uses a local syntax test; the described measurements require its function to evaluate correctly. Fallbacks keep readable spacing without simulating functions in JavaScript. Review the linked data before production use: the draft and implementations can change.