So it actually happened. After a decade of "CSS will replace preprocessors eventually," the @function at-rule is live in browsers. Not a polyfill. Not a PostCSS plugin. Native, runtime CSS functions that accept arguments and return computed values. I spent the past week converting a design system's Sass utilities and the results are mixed in the best possible way.

The Syntax, Quick

@function --spacing($multiplier) {
  result: calc(8px * $multiplier);
}

.card {
  padding: --spacing(2);       /* 16px */
  margin-bottom: --spacing(3); /* 24px */
}

Double-dash prefix, same convention as custom properties. A result: descriptor instead of Sass's @return. You call the function by its dashed name directly in property values — no wrapper, no call(). Arguments support defaults and type constraints. If you've written Sass functions before, you'll be productive in about ten minutes.

Color Manipulation Without a Build Step

This is where things get genuinely exciting. Every project I've touched in the last five years uses Sass's darken(), lighten(), or mix() somewhere — usually in a _colors.scss partial that nobody's updated since the original developer left.

The native replacement, using relative color syntax with oklch():

@function --tint($color, $amount: 20%) {
  result: oklch(from $color calc(l + $amount) c h);
}

@function --shade($color, $amount: 20%) {
  result: oklch(from $color calc(l - $amount) c h);
}

.btn-primary {
  background: var(--brand);
  border-color: --shade(var(--brand), 15%);
}
.btn-primary:hover {
  background: --tint(var(--brand), 10%);
}

Here's what Sass fundamentally cannot do: these functions execute at render time. Toggle --brand via JavaScript, switch it with a media query, let users pick a theme color from a color input — every tint and shade recalculates instantly. Sass bakes everything into static output at compile time. Once you need runtime flexibility, there's no comparison.

I migrated our theme system's color scale from 47 Sass-generated variables to 3 custom properties and 2 CSS functions. The compiled output dropped from 2.8KB to 340 bytes. The theme switcher went from "rebuild and swap a stylesheet" to "change one property."

The Math Library You Didn't Know CSS Had

Over the past two years, CSS quietly accumulated a serious collection of math functions. Combining them with @function unlocks patterns that previously required JavaScript or Sass plugins:

Function What You'd Use It For
round(), mod() Pixel-snapping fluid values, alternating row styles
pow(), sqrt() Exponential easing curves, nonlinear spacing scales
abs(), sign() Direction-aware transforms, guaranteed positive spacing
clamp(), min(), max() Already everywhere — but composable inside functions now

A fluid typography function that used to need a Sass plugin like poly-fluid-sizing:

@function --fluid($min, $max, $from: 320, $to: 1200) {
  result: clamp(
    $min,
    calc($min + ($max - $min) * (100vw - $from * 1px) / ($to - $from)),
    $max
  );
}

h1 { font-size: --fluid(1.5rem, 3rem); }
p  { font-size: --fluid(1rem, 1.25rem); }

Six lines. No dependencies. No compile step. And because this evaluates at runtime, it works inside container queries — something a preprocessor literally cannot do, because container dimensions don't exist at build time.

What Still Needs a Preprocessor

Before you npm uninstall sass, here's the honest inventory of what @function doesn't cover.

No mixins. Functions return a single value. You can't output multiple declarations from one call. The @mixin / @apply proposal is specified but hasn't shipped yet.

No loops or complex branching. There's an if() function for ternary expressions, but no @each, @for, or @while. Grid system generators and utility class factories stay in Sass for now.

No string operations. Sass's str-slice(), to-upper-case(), and string interpolation for building selectors? No CSS equivalent exists.

No partials. CSS @import fires a network request — it doesn't concatenate files. Your organizational strategy still benefits from a bundler or preprocessor.

Realistic estimate: @function replaces 40-60% of what most teams actually use Sass for. The structural code generation — loops, mixins, partials — still needs tooling.

@mixin Is Closer Than You Think

The CSS Working Group greenlighted @mixin / @apply, and the spec is progressing under CSS Mixins Module Level 1. Proposed syntax:

@mixin --visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  clip-path: inset(50%);
  overflow: hidden;
}

.sr-only {
  @apply --visually-hidden;
}

Open questions remain — whether variables leak from mixin scope into the caller, how specificity works when multiple mixins collide, what happens with circular references. But Chrome 146 is reportedly targeting an initial implementation, and the trajectory is unmistakable: CSS is methodically absorbing preprocessor features, and the pace has accelerated sharply since Interop 2026 locked in its priority list.

What I'd Actually Do Today

Greenfield project? Use @function for anything that computes a single value — color manipulation, spacing tokens, fluid sizing, responsive calculations. It's standardized, it eliminates a build dependency for a real chunk of styling logic, and it does things Sass structurally cannot (runtime evaluation, container-query awareness).

Working codebase with existing Sass? Don't refactor what works. Introduce @function alongside your preprocessor for new utilities, especially color and spacing. The two coexist without conflict — Sass ignores CSS function syntax it doesn't recognize, and the browser ignores Sass syntax that never reaches it.

And keep an eye on Lightning CSS, the Rust-based transformer that Vite 8 just adopted via Rolldown. It handles the remaining preprocessor patterns faster than Node-based Sass ever did. The endgame might not be "CSS replaces Sass" so much as "CSS handles runtime, Rust handles build-time, and the Node.js Sass compiler retires quietly."

Either way — your color functions don't need a compiler anymore. That's not nothing.