Back to all features
Open for signals CSS

@function

The @function CSS at-rule defines a custom function that takes CSS values or custom properties as arguments, and returns a CSS value. It can be based on conditional logic such as by using the @media at-rule.

Modern Web Guidance

How Modern Web Guidance uses this feature

Encapsulates reusable, parameterized style logic such as color gradient tokens and responsive grid templates into custom functions using the @function CSS at-rule and the result descriptor.

Recommended fallback strategy

Declare static CSS fallback values immediately before the property invoking the custom function, or utilize a build-time CSS preprocessor to compile function results for non-supporting engines.

Community use cases (9)

Community signal summary

Developers strongly advocate for native @function support to encapsulate reusable mathematical and styling logic within design systems [2][4]. Primary use cases include centralizing color manipulations—such as dynamic alpha adjustments [1][8] and OKLCH lightness or chroma scaling [6]—calculating fluid responsive typography with clamp() [5], managing component-scoped scaling units [7], and executing linear interpolation [8]. Engineers emphasize that custom functions would streamline complex styles, replace repetitive boilerplate with clean abstractions, and significantly reduce the volume of CSS shipped over the wire [4][9].

Without native @function capabilities, developers must endure substantial maintenance friction and code redundancy. Teams frequently repeat verbose inline formulas like relative color syntax (oklch(from ...)) [1][6] or complex calc() equations across multiple property declarations [2][7][8]. While some developers rely on preprocessors or consider authoring PostCSS or Lightning CSS build plugins [1][5][9], an engineer at Microsoft Corporation noted that for large-scale applications, the added build-step complexity and compilation overhead make preprocessor or plugin workarounds impractical compared to waiting for native cross-browser engine support [9].

What I want to do with this feature

I'd like an easy way to make a color partially transparent, like background-color: translucent(var(--m3c-on-background) / 0.08). I could literally define a --translucent function with @function.

What I'm having to do in the meantime

My two options are to write all colors as their RGB values (so I have to do rgb(var(--m3c-background)) and rgb(var(--m3c-on-background) / 0.08)) or to manually write rgb(from var(--m3c-on-background) r g b / 0.5). Both are so verbose I'm using a custom Vite plugin instead.

What I'm having to do in the meantime

In my design system, I have plenty of functions fr color palettes and sizing-scales, which use custom properties, and calc() and pow(). Without being able to inject parameters into these calculations, I currently have to redeclare each function for each property on every element with the * selector, e.g. --border-left-width: ..., --border-bottom-width: ..., --padding-inline: ... or --bg-color: ..., --ft-color: ..., etc.

What I want to do with this feature

With CSS functions, I could simply declare a single parametrized function once (instead of 30+ times the exact same function for each property), and reuse it whereever I need it.

Trying to keep CSS DRY and semantically valuable inevitably leads to use CSS variables to hold theme colors. Now components should respect their context and adapt, and functions would allow me to centralize the implementation detail of how I propagate my color instead of at every usage site. (Splitting currentColor and the text-color would help a lot, too)

What I want to do with this feature

I want to use @function to encapsulate complex styling logic in a reusable, DRY way.

This can significantly reduce CSS bundle sizes, reduce maintenance requirements, and promote uniformity.
It is compatible with any framework by construction, and usable directly in any form of markup (TSX, RSX, etc).
Even simple react apps that rely heavily on inline styles (which normally lack support for features like @media), can easily bake in complex, comprehensive styles without onerous build concerns.

This would be the single biggest impact CSS feature in my decade as a developer.

What I'm having to do in the meantime

I have to rely on extensive repetition of literal values (which bake in local deviations, because of an inability to pass a parameter), computations, and fragmented @media gates. This massively increases maintenance burden, makes behavior drift inevitable, and bloats CSS output size.

What I want to do with this feature

I'd like to create a function which takes a min and max number and a min and max viewport size (or default values) and returns a clamp() function with a smooth transition between the min and max using vw units.

What I'm having to do in the meantime

I'm currently using an online calculator to do the math and give me something like clamp(0.375rem, -1rem + 2.5vw, 1rem), which is annoying because its not easy to edit or tell at what viewport sizes the min and max are reached. --smooth-clamp(0.375, 1, 480, 1200) is much easier to read. I know I could use a preprocessor, but that's just extra work for one thing and it would be quite nice to have it supported natively.

Panicintrinsica
Missouri State University

What I want to do with this feature

Like others, I want to use functions to abstract out the more tedious, verbose and illegible design patterns that I regularly use in my design systems. The most basic example being derived colors, such that I can pass something like --clr-shift(var(--clr-primary), 10) to in/decrease chroma and lightness by 10%

What I'm having to do in the meantime

For the same effect, anytime I want to define a color offset somewhere, I have to write the same value as oklch(from var(--clr-primary), calc(l * 1.1) calc(c * 1.1) h / 1);, which is significantly longer, particularly when repeated 100+ times across the whole system.

What I want to do with this feature

Using rem units in css has a major advantage of scaling down the webpage by equal proportional in media query by reducing the root font size and thus reducing the overwriting code.

We have an architecture in css files while developing a webpage,

  1. We have a common css that holds the styling for common elements (global header-footer, etc.).
  2. We have a template css that holds the styling for webpages that comes under a particular template.
  3. We then have a product css that contains the styling of product level elements or components (product header-footer, etc).
  4. Page level css that contains the styling for that particular webpage (part that lies in between the header and footer).

Now, with this architecture, it is difficult for me to use rem units in css as changing the root font size in any css file under a media query would affect every other components differently and thus resulting in the decision of not changing the root font size at all. So, in order to achieve this advantage I could use a scoped custom property called "--page-zoom : 1" and a custom function called "--rem" which when passed a number as parameter results the value multiplied with the "--page-zoom" value.

I could now use --rem(5) instead of 5rem. This doesn't increase the verbosity and in media query, I could just change the value to "--page-zoom: 0.85" and this would scale the webpage proportionally as it would work when root font size is reduced but with the advantage of having this scaling applied only for that component.

What I'm having to do in the meantime

In the mean time, I have the option to use the calc() function along with var() function to achieve this, for example "padding: calc(5rem * var(--page-zoom))". But, this is more verbose compared to --rem(5) and using it everywhere would be a pain for developers.

What I want to do with this feature

  1. The most obvious advantage is reusability. I want to interpolate between two values; with @function, all I have to do is write this:
@function --lerp(--start, --end, --t) {
	result: calc(var(--start) + var(--t) * (var(--end) - var(--start)));
}

and use --lerp() wherever I want to interpolate between two values.

  1. I want to write cross-browser fallbacks with the at-rule. Let's take the abs() CSS function, it's newly available in all major browsers and if I want to write a fallback for older browsers using the max(var(--num), -1 * var(--num)) trick, I can declare:
@function --abs(--num) {
	result: abs(var(--num));
	@supports not (opacity: abs(-0.5)) {
		result: max(var(--num), -1 * var(--num));
	}
}

and use --abs() rather than the actual function until it's baseline. It would also be super easy to replace --abs() with abs() when it becomes baseline. Of course, abs() will be baseline before @function but the point is that writing fallbacks in general becomes much less of headache.

  1. With @function, I can write a function like the following:
@function --alpha(--color, --a) {
	result: oklch(from var(--color) l c h / var(--a));
}

and use it as --alpha(var(--custom-color), 0.5) rather than oklch(from var(--custom-color) l c h / var(--a)) which hides the lightness, chroma, and saturation parameters and makes it clear that only the alpha value is meant to be modified. It gives us better encapsulation, reduces the possiblity of mistakes from new developers, and is more in line with the principle of least privilege.

  1. In general, @function gives me more creative control. I can write new functionalities without waiting for CSS to roll them out and provide cleaner fallbacks for them. I can write my own polyfills. It gives me more incentive to use CSS.

What I'm having to do in the meantime

  1. Without @function, I'm having to repeat the formula everywhere, which is making it hard for me to breathe. And I also have to create a variable for the start value because it's used twice in the formula.

  2. Without @function, I'm having to repeat the fallback for each property I'm using the function in. If I'm using abs() for four properties, I'd have to write the fallback for each of those properties in the @supports block:

transform: scale(calc(1 - abs(var(--_switch)) * 0.2)) translateX(calc(var(--_switch) * 10%));
opacity: calc(1 - abs(var(--_switch)));
filter: blur(calc(abs(var(--_switch)) * 20px));
z-index: calc(1 - abs(var(--_switch)));

@supports not (opacity: abs(-0.5)) {
	transform: scale(calc(1 - max(var(--_switch), -1 * var(--_switch)) * 0.2)) translateX(calc(var(--_switch) * 10%));
	opacity: calc(1 - max(var(--_switch), -1 * var(--_switch)));
	filter: blur(calc(max(var(--_switch), -1 * var(--_switch)) * 20px));
	z-index: calc(1 - max(var(--_switch), -1 * var(--_switch)));
}

And that's all it takes for me to hate my codebase and my day.

  1. Without @function, I open up an avenue for future mistakes, violate the principle of least privilege, and just make the code worse.

  2. Without @function, I have less incentive to let CSS handle things. I have to wait for new features to not just be available but be baseline. I have to write ugly fallbacks. Even for simple mathematical formula like interpolation, I would rather do it in JS because it's the cleaner way.

spmonahan
Microsoft Corporation

What I want to do with this feature

Like others, my immediate need is to replace tedious boilerplate with a single function. This will reduce the amount of code I ship over the wire and simplify my CSS.

What I'm having to do in the meantime

I've considered writing a PostCSS or Lightning CSS plugin to support the basic, static function spec. This leaves out conditional logic but get me at least the primary features I want. I could also use Sass functions. However, for large scale apps, the additional complexity and build time just aren't worth it. I'm waiting for the native feature to be available in all the browsers I support.