Why CSS Needs Custom Functions

For years, CSS developers have relied on preprocessors like Sass to bring logic and reusability to stylesheets. But now, the platform is catching up. The CSS @function at-rule, part of the CSS Custom Functions and Mixins Module Level 1, lets you define your own functions directly in CSS — no build step required.

Imagine being able to write a function that calculates a progression percentage, adjusts spacing based on a scale, or even returns a color with alpha transparency. With @function, you can encapsulate complex logic and reuse it across your stylesheets.

This isn't just a syntactic sugar. It's a fundamental shift toward more maintainable, type-safe CSS. Let's dive into how it works and what you can do with it today.

Note: @function is still experimental. Check browser support before using it in production.

Developer writing CSS custom functions in a code editor Technical Structure Concept

Core Syntax and Practical Examples

Defining a Basic Function

A custom function starts with @function, followed by a --dashed-ident name, optional parameters, and a returns descriptor. Inside, you use the result descriptor to return a value.

/* Define a function that halves any length */
@function --half(--size) returns {
  result: calc(var(--size) / 2);
}

/* Use it */
.container {
  margin-inline: --half(20px); /* 10px */
}

Type Checking for Safety

You can enforce input types using angle brackets, similar to @property. This catches errors early, especially in large codebases.

/* Only accept numbers, return a percentage */
@function --progression(--current, --total) returns {
  result: calc(var(--current) / var(--total) * 100%);
}

.progress-bar {
  width: --progression(3, 5); /* 60% */
}

Handling Lists and Defaults

Passing lists is possible by suffixing # and wrapping values in curly braces. Default values are also supported.

/* Calculate range from a list plus an extra value */
@function --get-range(--list#, --n) {
  result: calc(max(var(--list)) - min(var(--list)) + var(--n));
}

div {
  padding-block: --get-range({10px, 100px, 50px, 25px}, 200px); /* 290px */
}

Nesting Functions and Local Variables

You can nest custom functions and use locally scoped custom properties, keeping your code modular and clean.

@function --square(--n) {
  result: calc(var(--n) * var(--n));
}

@function --circle-area(--radius) {
  --pi: 3.14159;
  result: calc(var(--pi) * --square(var(--radius)));
}

.blob {
  width: calc(--circle-area(10) * 1px); /* 314.159px */
}

Using the Cascade for Responsive Logic

Because result follows the CSS cascade, you can use media queries or container queries to return different values.

@function --suitable-font-size() returns {
  result: 16px;
  @media (width > 1000px) {
    result: 20px;
  }
}

body {
  font-size: --suitable-font-size();
}

Remember: The last valid declaration wins. If you place the fallback after the media query, it will always override it.

CSS @function at-rule example on a laptop screen Programming Illustration

Limitations and Gotchas

While @function is powerful, it has strict rules:

  • No side effects: You cannot change properties or generate multiple declarations. That's reserved for the proposed @mixin at-rule.
  • Circular dependencies: If a function calls itself (directly or indirectly), the browser marks it invalid. Same goes for custom properties that depend on the function.
  • Type mismatches: If an argument doesn't match the declared type, the entire function call becomes invalid.

Browser Support and Progressive Enhancement

Currently, @function is only supported behind flags in some browsers. Unsupported browsers ignore it, so you should use fallbacks and @supports for progressive enhancement.

@supports (at-rule(@function)) {
  /* Use custom functions */
}

However, note that the at-rule() condition itself has limited support (Chrome 148+ only). For the latest status, check Can I use.

CSS code snippet showing custom function syntax Development Concept Image

Conclusion and Next Steps

CSS @function is a game-changer for creating maintainable, logic-driven styles. Start experimenting with it in your local projects to get familiar with the syntax and capabilities. Once browser support improves, you'll be ready to adopt it in production.

For a deeper dive into modern CSS features, check out our guide on CSS container queries and practical examples of custom properties.

If you're interested in how to decide between modals and separate pages in UX design, we have a definitive decision tree that might help. And for those working with distributed systems, our PyTorch communication tutorial is a great resource.

Source: CSS-Tricks Almanac: @function

This content was drafted using AI tools based on reliable sources, and has been reviewed by our editorial team before publication. It is not intended to replace professional advice.