CSS is getting functions and conditionals: @function and if()
Two new pieces of CSS — custom functions and inline conditionals — remove the copy-paste that every component accumulates. Neither is Baseline yet, but both are worth reading early.
CSS picked up two new things: the ability to define functions, and the ability to write conditionals. Both are still experimental, but the duplication they replace exists in every project.
@function: a function that can read custom properties at runtime
@function --step(--n <number>: 1) returns <length> {
result: calc(var(--space-unit) * var(--n));
}
.card {
padding: --step(2);
}
The shape is @function --name(--param <type>: default) returns <type> { ... }, the return value is written as result:, and you call it as --name(args).
The difference from a SCSS function is not the syntax, it is when it evaluates:
| Evaluated | Can read var() |
|
|---|---|---|
| SCSS function | Build time | No — runtime values are invisible |
CSS @function |
Computed-value time | Yes |
That single row is where the value lives. Anywhere your tokens are custom properties and your function needs to compute from them, a preprocessor function simply cannot do it — it has no idea what var(--space-unit) will be. This is exactly what I have wanted ever since I moved this site’s theming onto a token system.
if(): three copies of a declaration, collapsed into one
.card {
grid-template-columns: if(
media(width < 40rem): 1fr;
else: 14rem 1fr;
);
}
It accepts three kinds of test: style() (a custom property’s value), media(), and supports(). It supports else, and multiple branches.
The style() form is the useful one:
.badge {
color: if(
style(--skin: ink): var(--c-accent);
style(--skin: riso): var(--c-ink-2);
else: var(--c-text);
);
}
Before this, that meant writing the same selector three times, once inside each [data-skin=...] block.
Where they stand: neither is Baseline
MDN labels both the same way: limited availability, not Baseline, experimental. The newest browsers support them; older devices do not.
For now the only safe way to use them is progressive enhancement, and the order matters:
.card {
/* old syntax first: browsers that cannot parse if() keep this one */
padding: 1rem;
padding: if(media(width > 60rem): 1.5rem; else: 1rem);
}
A value CSS cannot parse causes the entire declaration to be dropped, so an old browser quietly stays on the previous line. Write it the other way round and you have no fallback.
My call
if() is worth more than @function.
Conditional branches are the repetition every component runs into — breakpoints, skins, light and dark. Every new axis adds another layer of duplication. Extracting a function, on the other hand, only pays off in a handful of places; most calc() combinations are not more readable as a function.
One thing to resist: CSS is moving from purely declarative to “declarative plus a little programming”, but it is still cascade-first. Do not build a state machine out of these. The moment you find yourself modelling state transitions with if(), that logic did not belong in the stylesheet to begin with.

Comments
…