Skip to content

[css-values] Make min and max value optional in clamp() #9713

Closed

Description

@LeaVerou

As we know, we added clamp() because doing clamping logic with min() and max() is counterintuitive.
However, this doesn't only apply when you have both a min and a max, it’s still counterintuitive when you only have one of two. I keep finding myself using clamp() with dummy min or max values so that I can only use it for max or min respectively, which not only adds cognitive overhead, but also pointless computation.

A nice little improvement could be to make the min and max values optional via none keywords:

Before:

<clamp()> = clamp( <calc-sum>#{3} )

After:

<clamp()> = clamp( [ <calc-sum> | none ] , <calc-sum> , [ <calc-sum> | none ])
Alternative syntax: Using empty tokens for the omitted argument
<clamp()> = clamp( <calc-sum>? , <calc-sum> , <calc-sum>? )

Note that this is different from clamp( <calc-sum>#{2, 3} ): we still need empty tokens for the missing arguments, otherwise we wouldn't be able to disambiguate. I.e. clamp(3rem, 10vh) would not be valid, it would need to be either clamp(3rem, 10vh, ) or clamp( , 3rem, 10vh).
If we go that route we'd also need to add an exception to require the comma with empty arguments, per normal grammar specification rules the comma would be omitted, making disambiguation impossible.

When I first posted this I thought going the empty token route would be ideal, as none seemed too verbose, but I now think the clarity hit is not worth it, especially if we take the editorial challenges into account.

Hopefully this is such a small change we may be able to wedge it in css-values-4.

Edit: Agenda+ after discussing it with @fantasai and @tabatkins — this seems like such an easy win and pretty uncontroversial so we could move forwards with hopefully very little call time.

Activity

  1. tabatkins commented on Dec 15, 2023

    @tabatkins
    Member

    Yup, this makes a lot of sense. The confusion argument for introducing clamp() in the first place still applies when you're only clamping from one side, so making that easy seems like an obvious win.

    (I'm pretty strongly opposed to the alternative "just omit the argument, but keep the comma in" syntax, as that violates our comma-elision rules, and those exist for a good reason. But the none syntax is great.)

  2. ByteEater-pl commented on Jan 5, 2024

    @ByteEater-pl

    Why not just use max or min when you want to clamp from one side? What are the use cases for adding syntax to let clamp double as them? Code generation, perhaps?

  3. Loirooriol commented on Jan 5, 2024

    @Loirooriol
    Contributor

    @ByteEater-pl The idea is that min() and max() can be confusing, e.g. someone wants a calculation like var(--a) * var(--b) + var(--c) but enforcing a minimum of 0px. If they are thinking in terms of adding a minimum threshold they might be tempted to use min(), but actually they need max().

    But I'm not sure if this proposal will really help much, people who misunderstands what min() does will probably use it anyway, and people aware of this "gotcha" can just use max() indeed.

  4. LeaVerou commented on Jan 5, 2024

    @LeaVerou
    MemberAuthor

    @ByteEater-pl The idea is that min() and max() can be confusing, e.g. someone wants a calculation like var(--a) * var(--b) + var(--c) but enforcing a minimum of 0px. If they are thinking in terms of adding a minimum threshold they might be tempted to use min(), but actually they need max().

    But I'm not sure if this proposal will really help much, people who misunderstands what min() does will probably use it anyway, and people aware of this "gotcha" can just use max() indeed.

    Not necessarily. They might remember there is some weirdness around this, and this frees them from having to do the mental gymnastics. Or they may just reach for clamp() without considering min() or max() because they have internalized that that's what you use to introduce upper and lower bounds, and you use min() and max() when you need the minimum or maximum number among many. These are distinct use cases, and I'd argue using min() or max() for the former is actually a workaround, not a primary use case for min() and max().

  5. ByteEater-pl commented on Jan 6, 2024

    @ByteEater-pl

    I'd bet most uses of max and min are with two arguments, so it does seem primary.

  6. tabatkins commented on Jan 24, 2024

    @tabatkins
    Member

    The fact is still that max()/min() can be confusing to use, depending on what you're going for. If your intention is "use the largest of these two values", then max() is exactly what you need and completely intuitive. If your intuition is "use this value, but clamp it to a maximum of this second value", then the fact that you have to use min() for that is easy to get confused about. People regularly write that using max(), in my experience, and then are confused why that doesn't work.

    So for the "use this value, but clamped above/below by this second value", using clamp() with only one bound makes a lot more sense.

  7. css-meeting-bot commented on Feb 14, 2024

    @css-meeting-bot
    Member

    The CSS Working Group just discussed [css-values] Make min and max value optional in `clamp()` , and agreed to the following:

    • RESOLVED: Add 'none' keyword for upper/lower bounds of clamp().
    The full IRC log of that discussion <emilio> lea: the reason we introduced clamp() is that clamping with min() / max() is counter-intuitive, upper bound means min() and lower bound you need max()
    <emilio> ... however author still need min() / max() when only have a lower bound or upper bound
    <emilio> ... I found myself doing clamp(.., infinity) or such thing
    <fantasai> ... suggestion in issue is to add a 'none' keyword
    <emilio> ... so we discussed it in the issue, and it seemed like 'none' was straight-forward
    <emilio> ... I even found author infographics about how confusing this is
    <TabAtkins> +1 to this, as I said in the thread
    <emilio> q+
    <oriol> q+
    <astearns> ack emilio
    <TabAtkins> emilio: what's the reason for 'none' vs omission
    <fantasai> fantasai: beause there's commas involved
    <fantasai> TabAtkins: having an empty spot indicated only by presence of commas
    <florian> q+
    <fantasai> TabAtkins: is extremely awkward (and also violates comma elison rules)
    <dholbert> scribe+
    <dholbert> emilio (IRC): I was thinking you could clamp with 2 values
    <dholbert> TabAtkins (IRC): which is which, upper vs lower
    <astearns> ack oriol
    <dholbert> emilio (IRC): then 'none' makes sense
    <emilio> oriol: I kinda disagree with min() / max() being workarounds
    <emilio> ... they mathematically are the same, but I agree there's some confusion
    <emilio> ... not sure if this will completely solve it
    <emilio> ... they may not realize that clamp(none) can help it
    <emilio> ... this seems a pretty straight-forward generalization so seems fine, tho
    <emilio> lea: when I said workaround it's a UX argument, not a mathematical argument
    <astearns> ack florian
    <emilio> ... when something is used for it's non-primary purpose it's a workaround
    <lea> q+
    <fantasai> florian: from utility point of view, setting both lower and upper bound to none is useless, but should we still allow it in the syntax?
    <emilio> florian: setting both to none is useless
    <lea> q-
    <fantasai> TabAtkins: keep them both, because might be in a variable
    <emilio> ... but should we keep them?
    <fantasai> astearns: Seems we're all in violent agreement here
    <fantasai> RESOLVED: Add 'none' keyword for upper/lower bounds of clamp().
    <emilio> PROPOSAL: Allow none as firs or last arguments for clamp()
    <lea> 🎉
  8. ByteEater-pl commented on Feb 19, 2024

    @ByteEater-pl

    The CSS variables use case is convincing, I'm for. (It's like the code generation I mentioned, but built into the language.)

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions