You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
LESS if() function collides with CSS if() function #4384
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.
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.
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.
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:
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:
Find the try / catch block for function evaluation and have it not throw an error on bad function evaluation
Print a LessError but a "warning" style (there are deprecation warnings that currently do this)
Add multiple tests for not just if() but other "overlap" and "non-overlap" functions
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 functioncolor: 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);
}
"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 there's a similar (but even trickier) collision between LESS round() and CSS round(), which accepts 1-3 arguments instead of LESS's 1-2. I actually failed to find the string escaping workaround mentioned above and resorted to an inline import. Perhaps updating documentation or error messages to be more directional would be a good stopgap while determining and implementing a long-term solution?
indeed. for a case of round() I don't see other option than a flag, as was one of the propositions here.
native can have two numbers without units, so does LESS version and both are separated by a comma.
That can not be overloaded.
But I am not a member of LESS, I only posted this issue, and then realized it's broader and known problem as the native CSS is catching up.
For me, I like the idea of adding the prefix less.round() for all LESS functions which are present or coming in native CSS with the same name, but also that those that can be safely overloaded, would work without a prefix and with just a warning.
the escape syntax: margin: ~"round(-105px, 10px);"
@paponius@pydsigner The future design / behavior (v5) would be that all plain functions fail silently and simply dump their output, which Less already does for a number of functions, so that would be consistent. There are also some syntax updates that will make the author intent much more clear. But... I wonder if the former should happen sooner. It's technically a "breaking" change, but it only would break in the sense that if you MEANT to use a Less function, it wouldn't throw an error on you if you typed it wrong.
from the previous post (#4384 (comment))
this is how I understand the change:
when the code does not contain @use "#less";, functions like if() will fall through to native CSS, unless it's prefixed less.if(), in which case LESS takes over the less.if().
when there is @use "#less";, both if() and less.if() are handled by LESS.
I would like it this way, as the fix for older files would be to just add @use "#less";, basically saying, "accept the old syntax".
It's simpler and more solid solution than my idea of trying to parse it with LESS and if it fails on arguments, falling it through to native.
That would not break older code as much, but the confusion might cause more harm.
As to when, I am not a heavy LESS user. I only use it within the Stylus Extension (which has nothing to do with Stylus CSS).
I fought with the if() and after realizing what happened posted this issue. But since then, as I am aware of the behavior, it's not a biggie for me.
btw, I was using Stylus CSS before, and that was a hack on hack to solve some simple tasks.
After switching to LESS, what I appreciate the most is, the LESS is there when I need it, but does not get in the way when not.
when there is @use "#less";, both if() and less.if() are handled by LESS.
I'm not sure the best way to handle it. My thinking is that: if a @use (or whatever syntax) is present, then less.if() becomes a very clear signal of an author's intent. So, theoretically, if present, then a plain if() in the presence of ANY @use, would mean that the author is aware @use exists, and therefore, presumably, the if() (or any other plain functions) should NOT be handled by Less? Or, maybe, Less attempts to handle the function, but if it has an error, then the error is ignored. Whereas syntax errors with less.if() would NOT be ignored, because it's clear this is requesting the Less function, and not the CSS function.
I would like it this way, as the fix for older files would be to just add @use "#less";, basically saying, "accept the old syntax".
Basically, yes, that's what I'm thinking. Kind of an inherent, "if there are CSS function argument errors, ignore them and just dump the function in output".
My comment was coming from using which in some OOP would say, when I say myfun, use the myfun from the using namespace, as I will be using the object predominantly.
on a system with e.g. some.namespace.myfun and system.myfun,
keyword using would make aliases for functions on specified namespace, which will have precedence over others with the same name.
With using some.namespace, 'myfun' means some.namespace.myfun, while system.myfun is still possible, and so is full some.namespace.myfun
if inheriting that same logic, if keyword use (or using) would be desired, it would make more sense to make-up a name for native css instead.
e.g. @use plaincss, or rather @using plaincss to make it more obvious the logic of using is inherited.
and then why not also allow plaincss.if() as well as less.if().
or, avoid the keyword use. In favor of something along these keywords:
fully qualify (which is the term when talking about namespaces),
or fully qualified namespace, explicit prefix, prefixed only, strict namespace, syntax full
The question what to do when no @using or other keyword is specified, is a separate issue.
I believe none would want the behavior to change much, so if() would still be processed by LESS.
The question is whether it should be passed to plain css if LESS can't handle it. After a comprehensive support for both versions of functions is implemented, I am now undecided.
It's nice when stuff just works. But on the other hand, that's what I disliked on Stylus lang. Too benevolent.
If I make a mistake in parameters, not seeing errors where expected can be confusing.
A possible option, with no @using, fall-back functions to native css. With @using less, never fall back, but show error.
if inheriting that same logic, if keyword use (or using) would be desired, it would make more sense to make-up a name for native css instead. e.g. @use plaincss, or rather @using plaincss to make it more obvious the logic of using is inherited. and then why not also allow plaincss.if() as well as less.if().
I think it is desirable to remain backwards-compatible with plain CSS. If you have to prefix the built-in functions, then the migration from CSS to LESS is no longer seamless.
If you have to prefix the built-in functions
By saying backwards-compatible, I am sure you mean, that plain css will run in LESS with no issues.
And hence I assume, by build-in functions, you mean plain css functions.
Yes. that is what my proposal does.
You are citing from part of the post, where there is @using * keyword involved in the code.
That will not happen in plain css. If you define @using * the rest of the code is obviously LESS-specific and compatibility with plain css does not apply.
More arguable is the part mentioning situation where @using * is not present. e.g. plain css used in LESS.
This is the contradictory part we are mostly discussing here. The question is, what to adhere to.
On one hand, you have the statement you quoted, "Less (which stands for Leaner Style Sheets) is a backwards-compatible language extension for CSS.", and so, plain css should work the same when processed by LESS as it would when it's not.
On the other hand, there is the issue of LESS-backwards-compatibility. i.e. older LESS code will work in new LESS version.
Both can't be sustained for 100%.
That's because the newly introduced plain css functions collide with LESS functions.
We are trying to find less painful way to adjust to the situation.
These are all options, still talking about situation where there is not some specific directive or a keyword, e.g. @using * involved:
LESS will ignore all functions, not specifically marked as LESS functions,
LESS will process non-conflicting functions but ignore those present in both LESS and plain css (e.g. if()),
LESS will try to process all functions it was designed to process, and if it fails, will let plain css to try to use them as-is,
LESS will process all functions it was design to process, even conflicting ones, and fails if it can't,
LESS will process all functions, even those not supported by LESS at the moment, unless they are marked as non-LESS.
The choice 2. would keep backwards-compatibility with plain css, the choice 4. is the current state, and would mean backward-compatibility with previous versions of LESS.
choice 3 is something in between.
I was proponent of 3., but with the idea of adding @using * or something similar, I have no preference between 2-4.
Activity
dosubot commentedon Nov 11, 2025
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.
Share context across your team and agents. Try Dosu.
puckowski commentedon Nov 12, 2025
@paponius Thank you for the bug report.
My gut says this will be a tricky change to implement--allowing native
ifsupport 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.
matthew-dean commentedon Nov 12, 2025
We will have to support it, but note this:
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.
lppedd commentedon Nov 13, 2025
Ideally it's better to provide it transparently by changing how the
ifnode 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:
So you might be able to do something simiar for function usages:
matthew-dean commentedon Nov 13, 2025
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:
if()but other "overlap" and "non-overlap" functionsmatthew-dean commentedon Nov 13, 2025
@lppedd
The actual Less 5 syntax for this will probably look like this:
paponius commentedon Nov 13, 2025
"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.
pydsigner commentedon Mar 9, 2026
@paponius there's a similar (but even trickier) collision between LESS
round()and CSSround(), which accepts 1-3 arguments instead of LESS's 1-2. I actually failed to find the string escaping workaround mentioned above and resorted to an inline import. Perhaps updating documentation or error messages to be more directional would be a good stopgap while determining and implementing a long-term solution?paponius commentedon Mar 9, 2026
indeed. for a case of round() I don't see other option than a flag, as was one of the propositions here.
native can have two numbers without units, so does LESS version and both are separated by a comma.
That can not be overloaded.
But I am not a member of LESS, I only posted this issue, and then realized it's broader and known problem as the native CSS is catching up.
For me, I like the idea of adding the prefix less.round() for all LESS functions which are present or coming in native CSS with the same name, but also that those that can be safely overloaded, would work without a prefix and with just a warning.
the escape syntax:
margin: ~"round(-105px, 10px);"matthew-dean commentedon Mar 22, 2026
@paponius @pydsigner The future design / behavior (v5) would be that all plain functions fail silently and simply dump their output, which Less already does for a number of functions, so that would be consistent. There are also some syntax updates that will make the author intent much more clear. But... I wonder if the former should happen sooner. It's technically a "breaking" change, but it only would break in the sense that if you MEANT to use a Less function, it wouldn't throw an error on you if you typed it wrong.
Thoughts?
paponius commentedon Mar 22, 2026
from the previous post (#4384 (comment))
this is how I understand the change:
when the code does not contain
@use "#less";, functions likeif()will fall through to native CSS, unless it's prefixedless.if(), in which case LESS takes over theless.if().when there is
@use "#less";, bothif()andless.if()are handled by LESS.I would like it this way, as the fix for older files would be to just add
@use "#less";, basically saying, "accept the old syntax".It's simpler and more solid solution than my idea of trying to parse it with LESS and if it fails on arguments, falling it through to native.
That would not break older code as much, but the confusion might cause more harm.
As to when, I am not a heavy LESS user. I only use it within the Stylus Extension (which has nothing to do with Stylus CSS).
I fought with the
if()and after realizing what happened posted this issue. But since then, as I am aware of the behavior, it's not a biggie for me.btw, I was using Stylus CSS before, and that was a hack on hack to solve some simple tasks.
After switching to LESS, what I appreciate the most is, the LESS is there when I need it, but does not get in the way when not.
matthew-dean commentedon Mar 22, 2026
I'm not sure the best way to handle it. My thinking is that: if a
@use(or whatever syntax) is present, then less.if() becomes a very clear signal of an author's intent. So, theoretically, if present, then a plainif()in the presence of ANY@use, would mean that the author is aware@useexists, and therefore, presumably, theif()(or any other plain functions) should NOT be handled by Less? Or, maybe, Less attempts to handle the function, but if it has an error, then the error is ignored. Whereas syntax errors withless.if()would NOT be ignored, because it's clear this is requesting the Less function, and not the CSS function.Thoughts?
matthew-dean commentedon Mar 22, 2026
Basically, yes, that's what I'm thinking. Kind of an inherent, "if there are CSS function argument errors, ignore them and just dump the function in output".
paponius commentedon Mar 26, 2026
My comment was coming from
usingwhich in some OOP would say, when I say myfun, use the myfun from theusingnamespace, as I will be using the object predominantly.on a system with e.g. some.namespace.myfun and system.myfun,
keyword
usingwould make aliases for functions on specified namespace, which will have precedence over others with the same name.With
using some.namespace, 'myfun' meanssome.namespace.myfun, whilesystem.myfunis still possible, and so is fullsome.namespace.myfunif inheriting that same logic, if keyword use (or using) would be desired, it would make more sense to make-up a name for native css instead.
e.g.
@use plaincss, or rather@using plaincssto make it more obvious the logic of using is inherited.and then why not also allow
plaincss.if()as well asless.if().or, avoid the keyword use. In favor of something along these keywords:
fully qualify (which is the term when talking about namespaces),
or fully qualified namespace, explicit prefix, prefixed only, strict namespace, syntax full
The question what to do when no
@usingor other keyword is specified, is a separate issue.I believe none would want the behavior to change much, so
if()would still be processed by LESS.The question is whether it should be passed to plain css if LESS can't handle it. After a comprehensive support for both versions of functions is implemented, I am now undecided.
It's nice when stuff just works. But on the other hand, that's what I disliked on Stylus lang. Too benevolent.
If I make a mistake in parameters, not seeing errors where expected can be confusing.
A possible option, with no
@using, fall-back functions to native css. With@using less, never fall back, but show error.pydsigner commentedon Mar 26, 2026
I think it is desirable to remain backwards-compatible with plain CSS. If you have to prefix the built-in functions, then the migration from CSS to LESS is no longer seamless.
paponius commentedon Mar 26, 2026
Yes. that is what my proposal does.
You are citing from part of the post, where there is
@using *keyword involved in the code.That will not happen in plain css. If you define
@using *the rest of the code is obviously LESS-specific and compatibility with plain css does not apply.More arguable is the part mentioning situation where
@using *is not present. e.g. plain css used in LESS.This is the contradictory part we are mostly discussing here. The question is, what to adhere to.
On one hand, you have the statement you quoted, "Less (which stands for Leaner Style Sheets) is a backwards-compatible language extension for CSS.", and so, plain css should work the same when processed by LESS as it would when it's not.
On the other hand, there is the issue of LESS-backwards-compatibility. i.e. older LESS code will work in new LESS version.
Both can't be sustained for 100%.
That's because the newly introduced plain css functions collide with LESS functions.
We are trying to find less painful way to adjust to the situation.
These are all options, still talking about situation where there is not some specific directive or a keyword, e.g.
@using *involved:The choice 2. would keep backwards-compatibility with plain css, the choice 4. is the current state, and would mean backward-compatibility with previous versions of LESS.
choice 3 is something in between.
I was proponent of 3., but with the idea of adding
@using *or something similar, I have no preference between 2-4.pydsigner commentedon Mar 27, 2026
My preference I think would be for
3, with@using #lessenabling1.