← Blog

2026.10.22 · 5 min · css · browsers

CSS @function: write your own CSS functions.

CSS can now define named functions with typed parameters and defaults. How @function works, why it isn't a Sass function, the pitfalls that fail silently, and how to ship it while only Chromium supports it. As of 2026.10.

CSS can now define its own functions. You write the logic once, give it a name, and call it anywhere a value goes: padding: --double(8px);. No Sass, no build step. Today, reusable math in plain CSS means pasting the same calc() or oklch() formula into every rule that needs it. @function puts that formula in one place.

Watch the 60-second version on Instagram.

One formula, three copies

Say every surface that wants your brand colour see-through repeats the same relative colour formula. Three rules, three copies, and one of them with a different alpha:

before.css
.card  { background: oklch(from var(--brand) l c h / 0.8); }
.badge { background: oklch(from var(--brand) l c h / 0.8); }
.modal { background: oklch(from var(--brand) l c h / 0.5); }

With @function, the formula gets a name, a typed colour parameter and an alpha that defaults to 0.8:

after.css
@function --transparent(--color <color>, --alpha <number>: 0.8) returns <color> {
  result: oklch(from var(--color) l c h / var(--alpha));
}
.card  { background: --transparent(var(--brand)); }
.badge { background: --transparent(var(--brand)); }
.modal { background: --transparent(var(--brand), 0.5); }
BEFORE / 3 COPIES.card { background: oklch(from var(--brand) l c h / 0.8); }.badge { background: oklch(from var(--brand) l c h / 0.8); }.modal { background: oklch(from var(--brand) l c h / 0.5); }AFTER / 1 FUNCTION@function --transparent( --color <color>, --alpha <number>: 0.8) returns <color> { result: oklch(from var(--color) l c h / var(--alpha));}.card { background: --transparent(var(--brand)); }.badge { background: --transparent(var(--brand)); }.modal { background: --transparent(var(--brand), 0.5); }DASHED = REPEATED · GOLD = DEFINED ONCEFIG. 01 / THREE COPIES, ONE FUNCTION

Change the formula in the function and all three rules follow.

How it works

The full shape is @function --name(--param <type>?: <default>?, ...) returns <type>? { result: ...; }. Piece by piece:

  • The name is a dashed ident, like a custom property: --transparent. It's case-sensitive.
  • Parameters behave like locally scoped custom properties. Inside the body you read them with var(--color).
  • Types are optional. Leave out a parameter's type or the returns type and any value is accepted (type(*)). To accept more than one type, write type(<number> | <percentage>).
  • Defaults come after a colon: --alpha <number>: 0.8.
  • result is the return value. If there are several, the last one wins, so there's no early return. A result inside @media can override an earlier one, which gives you a responsive return value.
  • You call it by its bare name, with no var() around it: padding: --double(var(--space));.

The smallest useful function is one line:

double.css
@function --double(--v) { result: calc(var(--v) * 2); }
.box { padding: --double(8px); }

Defaults fill what you leave out

.card passes one argument. The function has two parameters, so --alpha falls back to its default, 0.8, and one colour comes out. .modal passes 0.5 and the default never applies.

@FUNCTION --TRANSPARENT--ALPHA / DEFAULT 0.8RESULTbackground: --transparent(var(--brand));GOLD = --COLOR · GREY = --ALPHA, NOT PASSEDFIG. 02 / THE DEFAULT FILLS THE GAP

The same fallback kicks in for an argument of the wrong type, if the parameter has a default. If it doesn't, see the first pitfall below.

It's not a Sass function, and it's not a mixin

The tempting reading is "Sass functions, now native". The difference is when it runs. A Sass function resolves at build time to a fixed value. @function runs in the browser, per element, with live custom properties and media queries. Change --brand on a subtree and every call inside it recomputes.

It's also not a mixin. A function returns one value, not a block of declarations. @mixin lives in the same spec, but it's a separate and much less stable part of it.

What people get wrong

  • A wrong argument type fails silently. The value becomes invalid at computed-value time, so background-color ends up transparent with no error anywhere. If the parameter has a default, an invalid argument falls back to that instead.
  • Commas inside an argument need braces. --max-plus-x({1px, 7px}, 3px) passes 1px, 7px as the first argument.
  • No recursion. A function that calls itself is a cycle, and it returns invalid.
  • It's Chromium only. Without a fallback, Firefox and Safari users get nothing.

Who supports it

Browser@function
Chrome and Edge 139+Yes, since August 2025
FirefoxNo, through 161
SafariNot in stable, through 27.2; it's in Safari Technology Preview

The spec is the CSS Functions and Mixins Module (css-mixins-1); its latest Editor's Draft is dated 14 Sep 2026, and MDN labels the feature experimental, not Baseline. It has been proposed for Interop 2027 (issue #1357), but it is not selected yet: proposals closed on 23 Sep 2026 and the final scope is due in early Feb 2027. Josh Comeau put it first on his personal Interop 2027 wishlist.

So for now, put a plain declaration in front of the call, or wrap the call in @supports:

fallback.css
.card {
  background: oklch(from var(--brand) l c h / 0.8);
  background: --transparent(var(--brand));
}

On a client project I'd use @function today only where a plain fallback declaration sits right in front of the call, and keep anything the layout depends on in plain CSS until Firefox and Safari ship it.

The short version

  1. @function --name(--param <type>: <default>) returns <type> { result: ...; } defines a CSS function; call it by its bare name, no var().
  2. It runs in the browser, per element, with live custom properties and media queries; Sass functions run at build time.
  3. It returns one value, not a block of declarations; that's @mixin.
  4. Wrong argument types fail silently, commas need {}, and recursion returns invalid.
  5. Chrome and Edge 139+ only; proposed for Interop 2027, not selected. Ship it behind a fallback declaration or @supports.

Sources: MDN, @function · CSS Functions and Mixins Module Level 1 (Editor's Draft) · New in Chrome 139 · caniuse, CSS @function · Interop issue #1357, CSS @function · Microsoft Edge blog, Calling for Interop 2027 proposals · Josh W. Comeau, The Future of CSS (Interop 2027)