Creating a clamp() fallback function in Sass/SCSS

Rather than using calc() and media queries to create fluid elements within min/max allowable values, on a new project I wanted to determine whether the new CSS functions of min(), max() and clamp() could be used instead.

Browser support is pretty good across all modern browsers, though clamp() isn’t available in all but the very latest version of Safari (at time of writing). With that in mind, I wanted to ensure suitable fallbacks were in place. The result was the following…

font-size:24px; font-size:min(max(12px, 3vw), 24px); font-size:clamp(12px,3vw,24px);

I didn’t want to discount older browsers like IE11 and Opera Mini completely as they don’t support any of these new functions at all, hence the standard font-size fallback.

It might be overkill to also provide a fallback for browsers that do support min() and max() but not clamp() through combining the min() and max() functions together, but as this is my first foray into using these on a live project I decided an element of caution was ok.

Min what? Max where? Clamp who?

If you’re unaware of the min(), max() and clamp() functions, they work like this…

padding-top:min(10vw, 100px); padding-top:max(10vw, 100px);

Essentially the max() function determines which of the two values is the largest and uses that. In the example above we’re specifying 100px as the maximum value padding-top can be, so if 10vw becomes less than this the browser will set the value as 100px, otherwise it will be whatever 10vw could be, which on a 2000px viewport would be 200px.

min() works the opposite way, selecting the smallest amount below the maximum allowable value we set.

padding-top:clamp(100px, 10vw, 200px);

For clamp() we include three parameters (min, value, max). The value is the one the browser will look to first, this is what we want padding-top to be in this case, however if that value becomes smaller than the first parameter, or larger than the last parameter then the browser will use the appropriate min or max value.

Using Sass/Scss

One challenge came when attempting to use the min() and max() functions with Sass. As Sass already had a min() and max() functions the use of the standard CSS min() function would cause an error. While different solutions were offered through the web, ultimately, the following was the solution I needed…

padding-top:#{"min(100px,10vw)"};
/* or */
padding-top:#{"min(max(100px, 10vw), 200px)"}

Not being able to use the function as standard was a bit of a pain really, but it did help lead me towards to creating a mixin to simplify doing this and more, at least when it came to using clamp().

The Mixin

@mixin clamp($property, $min-size, $scaler, $max-size, $min-size-left:false, $scaler-left:false, $max-size-left:false){
  @if $min-size-left == false {
    #{$property}:$max-size; 
    #{$property}:#{"min(max(#{$min-size}, #{$scaler}), #{$max-size})"}; 
    #{$property}:clamp($min-size, $scaler, $max-size);
  } @else if $min-size-left == 0 or $min-size-left == auto{
    #{$property}:$max-size $min-size-left; 
    #{$property}:#{"min(max(#{$min-size}, #{$scaler}), #{$max-size})"} $min-size-left;
    #{$property}:clamp($min-size, $scaler, $max-size) $min-size-left;
  } @else {
    #{$property}:$max-size $min-size-left; 
    #{$property}:#{"min(max(#{$min-size}, #{$scaler}), #{$max-size})"} #{"min(max(#{$min-size-left}, #{$scaler-left}), #{$max-size-left})"}; 
    #{$property}:clamp($min-size, $scaler, $max-size) clamp($min-size-left, $scaler-left, $max-size-left);
  }
}

This mixin function allows the following use cases…

/* Simple one value */
@include clamp('font-size',12px,3vw,24px);

/* Which outputs... */
font-size:24px; font-size:min(max(12px, 3vw), 24px); font-size:clamp(12px,3vw,24px);


/* Margin-top simple one value */
@include clamp('margin-top',100px,10vw,200px);

/* Which outputs... */
margin-top:100px; margin-top:min(max(100px, 10vw), 200px); margin-top:clamp(100px,10vw,200px);


/* Margin shorthand [value] [0/auto] */
@include clamp('margin',100px,10vw,200px,0);

/* Which outputs... */
margin:200px 0; margin:min(max(100px, 10vw), 200px) 0; margin:clamp(100px,10vw,200px) 0;
/* * Unfortunately margin:[0/auto] [value] isn’t supported.*/


/* Margin shorthand with values for top/bottom & left/right */
@include clamp('margin',100px,10vw,200px,50px,5wv,100px);

/* Which outputs... */
margin:200px 100px; margin:min(max(100px, 10vw), 200px) min(max(50px, 5vw), 100px); margin:clamp(100px,10vw,200px) clamp(50px,5vw,100px);

In addition to providing the fallbacks shown in the first code example of this article, the mixin also allows you to specify the property you want to use as well as handle shorthand values where you’re passing one or two values.

In future I may drop the min()/max() reference in favour of clamp() only, but for now it’s a useful little function to quickly author fluid measurements with appropriate fallbacks.


We'd love to hear from you!

If you think Bronco has the skills to take your business forward then what are you waiting for?

Get in Touch Today!

Discussion

Write a comment...
  • Akash Yadav

    Thank you for this amazing mixin.
    But i’m getting the same result for :
    1. @include clamp(‘font-size’,12px,3vw,24px);
    2. @include clamp(font-size,12px,3vw,24px);

    output
    font-size: 24px;
    font-size: min(max(12px, 3vw), 24px);
    font-size: clamp(12px, 3vw, 24px);

    but the way of using your property gives output like this
    3. @include clamp(‘font-size’,12px,3vw,24px);

    output
    ‘font-size’: 24px;
    ‘font-size’: min(max(12px, 3vw), 24px);
    ‘font-size’: clamp(12px, 3vw, 24px);

    • Kean Richmond

      Sorry, bad formatting between apostrophes and single quotes. I’ve updated the examples

  • Akash Yadav

    Can you please update this with !important property aswell

  • Akash Yadav

    My final code includes !important aswell.

    @mixin clamp($property, $min-size, $scaler, $max-size, $important:””, $min-size-left:false, $scaler-left:false, $max-size-left:false) {
    @if $min-size-left == false {
    #{$property}: $max-size #{$important};
    #{$property}:#{“min(max(#{$min-size}, #{$scaler}), #{$max-size})”} #{$important};
    #{$property}: clamp($min-size, $scaler, $max-size) #{$important};
    }

    @else if $min-size-left == 0 or $min-size-left == auto {
    #{$property}: $max-size $min-size-left #{$important};
    #{$property}:#{“min(max(#{$min-size}, #{$scaler}), #{$max-size})”} $min-size-left #{$important};
    #{$property}: clamp($min-size, $scaler, $max-size) $min-size-left #{$important};
    }

    @else {
    #{$property}: $max-size $min-size-left #{$important};
    #{$property}:#{“min(max(#{$min-size}, #{$scaler}), #{$max-size})”} #{“min(max(#{$min-size-left}, #{$scaler-left}), #{$max-size-left})”} #{$important};
    #{$property}: clamp($min-size-left, $scaler-left, $max-size-left) #{$important};
    }
    }

    • Kean Richmond

      More often than not I try to steer clear of using !important, but having the option if needed seems like a good enhancement

  • Davide

    Hi,
    I think the code “@mixin clamp” above must be checked because it lacks some #{}, left parameter doesn’t work as expected (only one clamp is called ) and the fallback rule mix min and max values (this is not wrong but I don’t understand why not use only min or only max values here #{$property}: $max-size $min-size-left).

    This is my changes (and it works for me):

    @mixin clamp($property, $min-size, $scaler, $max-size, $min-size-left:false, $scaler-left:false, $max-size-left:false){
    @if $min-size-left == false {
    // Set MAX values
    #{$property}: $max-size;
    #{$property}: #{“min(max(#{$min-size}, #{$scaler}), #{$max-size})”};
    #{$property}: clamp(#{$min-size}, #{$scaler}, #{$max-size});
    } @else if $min-size-left == 0 or $min-size-left == auto{
    // Set MAX values
    #{$property}: $max-size $max-size-left;
    #{$property}: #{“min(max(#{$min-size}, #{$scaler}), #{$max-size})”} $min-size-left;
    #{$property}: clamp(#{$min-size}, #{$scaler}, #{$max-size}) $min-size-left;
    } @else {
    // Set MAX values
    #{$property}:$max-size $max-size-left;
    #{$property}:#{“min(max(#{$min-size}, #{$scaler}), #{$max-size})”} #{“min(max(#{$min-size-left}, #{$scaler-left}), #{$max-size-left})”};
    // add second CLAMP and #{} to SASS variables
    #{$property}: clamp(#{$min-size}, #{$scaler}, #{$max-size}) clamp(#{$min-size-left}, #{$scaler-left}, #{$max-size-left});
    }
    }

    BTW thanks for this amazing mixin!

    • Kean Richmond

      Thanks for the nudge to check the code. You’re right that my original example was missing a second clamp() on the final @else. However in my tests I’ve not had any issues requiring the #{} within clamp(), so I haven’t included this for now. But your code will hopefully provide an additional example to anyone else experiencing similar issues with the original

  • Davide

    hi,
    your mixin lay the foundation of my small library [https://github.com/DidoMarchet/scss-utopia](https://github.com/DidoMarchet/scss-utopia). I credited you 🙂

    • Kean Richmond

      That’s great, hopefully it will help many others now too 🙂

  • Bram

    Great mixin, thanks for sharing! Progressive enhancement FTW

  • Almeida

    I never write a comment in any post, however this is an exception. THANK YOU SOOOOOO MUCH!!!!! You literally saved my life

  • Francetus

    Hello. Thank you for your great mixin !
    I have an issue. When I use it for margins or paddings, it doesn’t read the fallbacks. It writes only the clamp() and nothing else, so I have nothing for the old browsers.
    It works perfectly for all the properties, but not for theses ones. Do you have the same result ?
    Thanks a lot !

    • Kean Richmond

      I’m not aware of any issues, but if you had an example we could take a look.

      Though it couldn’t be added within the mixin, you could use the mixin within a feature query (@supports), in addition to a fallback outside the query, to help force the correct margin/padding on older browsers.

Add a Comment