Why CSS Custom Functions Matter
For years, CSS developers relied on preprocessors like Sass to bring logic and reusability to stylesheets. But the web platform is catching up. The new CSS @function at-rule (part of the CSS Custom Functions and Mixins Module Level 1) allows you to define native custom functions that accept arguments, perform calculations, and return values — all without any build step.
This is a paradigm shift. Instead of writing repetitive calc() expressions or maintaining Sass libraries, you can now create clean, self-documenting CSS functions that work directly in the browser. Let's explore how.
Source: CSS-Tricks Almanac: @function

Core Syntax & First Example
The @function rule uses a dashed-ident name (like --my-function), optional parameters with type checking, a returns descriptor, and a result descriptor inside the body.
/* Define a function that halves any length value */
@function --half(--size <length>) returns <length> {
result: calc(var(--size) / 2);
}
/* Use it */
.container {
margin-inline: --half(20px); /* resolves to 10px */
}
Type Checking
Just like @property, you can enforce argument types:
@function --progression(--current <number>, --total <number>) returns <percentage> {
result: calc(var(--current) / var(--total) * 100%);
}
.progress-bar {
width: --progression(3, 5); /* 60% */
}
Comma-Separated Lists
Pass a list as a single argument by wrapping it in curly braces and suffixing # to the parameter:
@function --get-range(--list<length>#, --n <length>) returns <length> {
result: calc(max(var(--list)) - min(var(--list)) + var(--n));
}
div {
padding-block: --get-range({10px, 100px, 50px, 25px}, 200px); /* 290px */
}
Conditional Logic with Cascade
The result descriptor respects the cascade. You can use @media, @container, or @supports inside the function:
@function --suitable-font-size() returns <length> {
result: 16px;
@media (width > 1000px) {
result: 20px;
}
}
body {
font-size: --suitable-font-size();
}
Nesting Functions & Default Values
@function --square(--n <number>) returns <number> {
result: calc(var(--n) * var(--n));
}
@function --circle-area(--radius <number>) returns <number> {
--pi: 3.14159;
result: calc(var(--pi) * --square(var(--radius)));
}
@function --brand-glass(--opacity: 0.5) returns <color> {
result: rgb(10 120 255 / var(--opacity));
}
.header {
background: --brand-glass(); /* 0.5 opacity */
}

Limitations & Caveats
- No side effects:
@functioncan only return a value. You cannot change properties or generate multiple declarations inside a function. For that, look to the proposed@mixinat-rule. - Circular dependencies: If Function A calls Function B and vice versa, both become invalid. The browser detects this and prevents infinite recursion.
- Browser support is experimental: Currently only Chrome 148+ supports
@functionnatively. The@supports (at-rule(@function))check is also not fully supported across browsers yet. - Fallback strategy: Always provide fallback declarations for unsupported browsers. For example:
.element {
width: 60%; /* fallback */
width: --progression(3, 5);
}
Next Steps
- Experiment with
@functionin Chrome Canary or with thechrome://flags/#enable-experimental-web-platform-featuresflag. - Combine with
@propertyfor even more type safety and custom property registration. - Stay tuned for the
@mixinproposal, which will unlock multi-line, stateful CSS logic.

Conclusion
CSS @function is a game-changer for maintainable, logic-driven stylesheets. It reduces reliance on preprocessors, improves readability, and brings true programming constructs to CSS — all without JavaScript. While still experimental, it's worth learning now to be ready for the future of web styling.
Related articles: