DEV Community

Cover image for CSS @function
Koushik Radhakrishnan
Koushik Radhakrishnan

Posted on

CSS @function

CSS just got its biggest quality-of-life upgrade since custom properties.

For years, if you wanted reusable logic in CSS, you had two choices: wrestle with custom properties that couldn't do real computation, or reach for a preprocessor like Sass. That era just quietly ended. CSS now has native custom functions — and they're as powerful as you'd hope.


1. What Exactly Is @function?

Think of it exactly like a JavaScript function — but living inside your stylesheet. You give it a name (always prefixed with --), define some parameters, and write a result: descriptor that determines what it returns.

/* BASIC SYNTAX */

/* Define it once */
@function --double(--x) {
  result: calc(var(--x) * 2);
}

/* Call it anywhere */
.box {
  width: --double(20px);  /* → 40px */
  height: --double(50px); /* → 100px */
}
Enter fullscreen mode Exit fullscreen mode

No var() wrapper needed to call it. No build step. No Sass import. Just define and use — as many times as you like, across your entire stylesheet.

Note: Unlike Sass, native CSS functions don't evaluate math automatically. You must wrap mathematical operations in calc() inside your result descriptor.


2. The Syntax in Full Detail

Parameters with Default Values

You can make parameters optional by giving them a fallback — the function still works even if you don't pass a value.

@function --spacing(--size: 16px) {
  result: calc(var(--size) * 1.5);
}

.card {
  padding: --spacing();      /* → 24px (uses default) */
  margin: --spacing(20px);  /* → 30px */
}
Enter fullscreen mode Exit fullscreen mode

Type-Safe Parameters

You can declare the expected type of each parameter — and the return type too. The browser will validate this at parse time, preventing weird cascading bugs.

@function --fade(
  --color <color>,
  --alpha <number>: 0.5
) returns <color> {
  result: color-mix(
    in srgb,
    var(--color),
    transparent calc(var(--alpha) * 100%)
  );
}

.overlay {
  background: --fade(royalblue, 0.3); /* 70% opaque blue */
}
Enter fullscreen mode Exit fullscreen mode

Logic Inside Functions

This is where @function gets genuinely exciting. You can embed @media or @supports queries right inside a function to return different values based on conditions.

@function --responsive-font(--sm, --lg) {
  result: var(--sm); /* mobile default */

  @media (min-width: 768px) {
    result: var(--lg); /* larger screen override */
  }
}

h1 { font-size: --responsive-font(24px, 48px); }
p  { font-size: --responsive-font(15px, 18px); }
Enter fullscreen mode Exit fullscreen mode

3. Real-World Examples

Example 1 — Theme-Aware Sizing (Light/Dark)

The built-in light-dark() function only works with color values. With @function, you can extend that same idea to any CSS property — font weights, border radius, letter spacing, anything.

@function --theme-val(--light, --dark) {
  result: var(--light);

  @media (prefers-color-scheme: dark) {
    result: var(--dark);
  }
}

button {
  font-weight: --theme-val(500, 400);
  border-radius: --theme-val(6px, 2px);
  letter-spacing: --theme-val(0em, 0.03em);
}
Enter fullscreen mode Exit fullscreen mode

Example 2 — Color Opacity Shorthand

Stop writing color-mix() from scratch every time you need a translucent variant of a color.

@function --alpha(
  --c <color>,
  --a <number>: 0.5
) returns <color> {
  result: color-mix(
    in srgb,
    var(--c) calc(var(--a) * 100%),
    transparent
  );
}

.modal-bg { background: --alpha(#0f0f0f, 0.7); }
.chip      { background: --alpha(royalblue, 0.15); }
.highlight { background: --alpha(gold, 0.3);      }
Enter fullscreen mode Exit fullscreen mode

4. Why This Beats Sass/SCSS for These Use Cases?

Let's be clear — Sass isn't going away. But @function directly replaces its most common use case: reusable value computation. Here's why the native approach wins:

  • No build step. Your function evaluates at runtime in the browser, not at compile time. It can react to live conditions like viewport size or user preferences.
  • Works with custom properties. You can pass var(--token) directly as a function argument. Sass couldn't dynamically read runtime custom property values.
  • Type safety. Declare parameter types and return types. The browser validates them — something Sass never did natively.
  • Ships in your CSS. Devtools can inspect it, browser can cache it. Zero toolchain friction.

5. Browser Support & Current Limitations

🔴 @function shipped in Chrome 139 (May 2025) and is supported across Chrome, Edge, and Opera (~67% of global users). Firefox and Safari are still pending — production use without a fallback affects ~33% of users.

🚫 No recursion. A function cannot call itself. Recursive patterns need to stay in JavaScript.

🚫 No side effects. Functions can only compute and return a value. You can't modify a DOM element's class or trigger an animation directly from inside a function.

⚠️ Name must start with --. Easy to forget. --my-fn() works; myFn() does not.

⚠️ Spec is still evolving. Some details (like interaction with cascade layers) are still being refined in the css-mixins-1 spec. Treat this as stable-but-maturing.

⚠️ No cross-file imports natively. Functions defined in one CSS file are not automatically available in another unless you use @import or bundle them together.

💡 Progressive Enhancement Strategy
Use @supports to check for @function support, or define a plain var() fallback first and layer your function-based values on top. Users on unsupported browsers will get the fallback; Chrome/Edge users get the enhanced version.


If this was useful, share it with your frontend team. Drop a comment with the most creative use case you can think of — I'd love to see what people build with this. 👇

Top comments (0)