Skip to content

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

Open

Description

@paponius

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.

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

Activity

  1. dosubot commented on Nov 11, 2025

    @dosubot

    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.

    Leave Feedback Learn about less.js with Dosu Add Dosu to your team

  2. puckowski commented on Nov 12, 2025

    @puckowski
    Contributor

    @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.

  3. matthew-dean commented on Nov 12, 2025

    @matthew-dean
    Member

    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.

  4. lppedd commented on Nov 13, 2025

    @lppedd

    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);
    
  5. matthew-dean commented on Nov 13, 2025

    @matthew-dean
    Member

    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
  6. matthew-dean commented on Nov 13, 2025

    @matthew-dean
    Member

    @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); 
    }
  7. paponius commented on Nov 13, 2025

    @paponius
    Author

    "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.

  8. pydsigner commented on Mar 9, 2026

    @pydsigner

    @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?

  9. paponius commented on Mar 9, 2026

    @paponius
    Author

    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);"

  10. matthew-dean commented on Mar 22, 2026

    @matthew-dean
    Member

    @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?

  11. paponius commented on Mar 22, 2026

    @paponius
    Author

    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.

  12. matthew-dean commented on Mar 22, 2026

    @matthew-dean
    Member

    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.

    Thoughts?

  13. matthew-dean commented on Mar 22, 2026

    @matthew-dean
    Member

    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".

  14. paponius commented on Mar 26, 2026

    @paponius
    Author

    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.

  15. pydsigner commented on Mar 26, 2026

    @pydsigner

    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.

  16. paponius commented on Mar 26, 2026

    @paponius
    Author

    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:

    1. LESS will ignore all functions, not specifically marked as LESS functions,
    2. LESS will process non-conflicting functions but ignore those present in both LESS and plain css (e.g. if()),
    3. 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,
    4. LESS will process all functions it was design to process, even conflicting ones, and fails if it can't,
    5. 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.

  17. pydsigner commented on Mar 27, 2026

    @pydsigner

    My preference I think would be for 3, with @using #less enabling 1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions