less.js icon indicating copy to clipboard operation
less.js copied to clipboard

LESS if() function collides with CSS if() function

Open paponius opened this issue 2 months ago • 7 comments

To reproduce: A property with an if() function using native CSS syntax in LESS space.

	color: if(style(color: none): blue; else: red);

Current behavior:

Error: Could not parse call arguments or missing ')'

Expected behavior:

A possibility to use the native CSS if() function directly, without a hack: color: ~"if(style(color: none): blue; else: red);"

Environment information:

Used within Stylus browser extension.

  • less version: 4.4.2 (based on https://github.com/openstyles/stylus/blob/master/package.json)
  • nodejs version: unknown
  • operating system: any

Proposed solution:
Determine whether LESS parser should process if() function or pass it on, by its syntax.
LESS syntax: three parameters https://lesscss.org/functions/#logical-functions-if
native CSS syntax: one comma separated "parameter" https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/if#syntax

paponius avatar Nov 11 '25 17:11 paponius

Hi @paponius! I'm Dosu and I’m helping the less.js team.

LESS's if() function expects three comma-separated parameters (condition, true value, false value), which conflicts with the native CSS if() function syntax (single parameter, semicolon-separated clauses). When you use the CSS if() syntax in LESS, the parser tries to interpret it as the LESS function and throws a parse error because the syntax doesn't match what it expects.

Currently, LESS does not distinguish between its own if() and the native CSS if(); any use of if() is parsed as the LESS function. The only supported workaround is to use LESS's escaped string syntax (~"...") to output the native CSS if() function as raw CSS, bypassing LESS parsing—for example: color: ~"if(style(color: none): blue; else: red);" reference.

There is no evidence in the code, documentation, or changelogs for LESS 4.4.2 that support for distinguishing or passing through native CSS if() syntax has been added or is planned. This issue also affects other modern CSS functions with colliding names, and the workaround is the same: use string interpolation to emit raw CSS reference.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

dosubot[bot] avatar Nov 11 '25 17:11 dosubot[bot]

@paponius Thank you for the bug report.

My gut says this will be a tricky change to implement--allowing native if support controlled by a flag or some configuration setting.

I'll keep this issue in mind during my free cycles where I have time to implement Less enhancements, but I am currently prioritizing a few other bug reports first.

puckowski avatar Nov 12 '25 22:11 puckowski

We will have to support it, but note this:

Experimental: This is an experimental technology

We won't / shouldn't be adding experimental CSS features.

Caniuse.com says this is nowhere on the roadmap for Safari nor Firefox, so it wouldn't be added before widespread adoption. It's unclear why anyone would want to use it if neither Safari nor Firefox support it. It only landed experimentally in Chromium in May of this year.

matthew-dean avatar Nov 12 '25 23:11 matthew-dean

by a flag or some configuration setting

Ideally it's better to provide it transparently by changing how the if node is parsed, for example by wrapping the usage with an added context. Going forward there might be multiple conflicting functions, so a way to "escape" or "switch on/off" the native CSS context is a good approach imo.

This kind of additional context is already present with imports:

@import (css) "...";

So you might be able to do something simiar for function usages:

color: css#if(style(color: none): blue; else: red); // or
color: css@if(style(color: none): blue; else: red);

lppedd avatar Nov 13 '25 11:11 lppedd

I hear you, but probably what we'll do is just make the behavior similar to Sass's, which is that failures in function signature maybe fail with a console warning but output as-is. We already do something similar for other functions that were added later to CSS, but this should probably be a universal behavior.

So if you want to do a fix for this, it would be:

  1. Find the try / catch block for function evaluation and have it not throw an error on bad function evaluation
  2. Print a LessError but a "warning" style (there are deprecation warnings that currently do this)
  3. Add multiple tests for not just if() but other "overlap" and "non-overlap" functions

matthew-dean avatar Nov 13 '25 17:11 matthew-dean

@lppedd

Ideally it's better to provide it transparently by changing how the if node is parsed, for example by wrapping the usage with an added context.

The actual Less 5 syntax for this will probably look like this:

@use "#less";

.box {
  // syntax error, because we are signaling this should be the Less function
  color: less.if(style(color: none): blue; else: red);
  // we're using a @use, therefore we are signaling anything else falls through as a CSS function 
  color: if(style(color: none): blue; else: red); 
}

matthew-dean avatar Nov 13 '25 18:11 matthew-dean

"Less is a backwards-compatible language extension for CSS."
"valid CSS is valid Less code with the same semantics."

A valid point is, how standard the native if() is. It was in the CSS draft, it's not something Chrome just made-up, but I started to use it expecting Firefox will follow soon. They ignore it. Also, there are more features expected to be added to if(). e.g. logical and comparison operators. I don't expect Chrome to drop it, but it might be Firefox will keep ignoring it, in which case users will have to drop it.

Transparently in my understanding means to do something on one layer without the need to care about, or even understand what is happening automatically on another layer. https://www.google.com/search?q=transparently+meaning

That means no directives to LESS or special syntax specifying what should be done with an expression.

Though I agree with the meaning, that LESS should handle all functions and conflicting cases consistently.

Function overloading is common in programming languages. Having multiple function declarations with different parameter sets. Count and/or types. If we consider only a comma (not a space) to be the parameter separator, LESS can "overload" the native if() in case there are three parameters, or pass it on literally.

I don't know which other functions collide, I compared, not thoroughly, https://lesscss.org/functions/ and https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/Functions e.g. hsla() uses spaces to separate parameters in native CSS. And similar to if() case, they can be perceived as one comma separated parameter by LESS and passed on. e.g. contrast() can have one comma separated parameter in both, but in native CSS it's "<number> or a <percentage>" and in LESS it's a color.

As the LESS is an extension for CSS, LESS's functions could this way be extensions of native CSS functions. The native parameter could be added to Doc and user can even think the common naming was done on purpose.

Not helpful for future proofing, as it's not certain e.g. if(), contrast() will not have second comma separated parameter added later, is this: "In some functional notations multiple arguments are separated by commas, while others use spaces." https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/Functions

But still, it might not be very likely all parameters will be of the same type.
And if such case arise, I would rather have the function name changed, or parameter set of the LESS function changed,
than the need to add a prefix, directive or change of a config. It would be a breaking change in any case (old code will stop working) and I like LESS is intuitive, CSS like, and not complicated from the user perspective.

paponius avatar Nov 13 '25 21:11 paponius