Bryan Rasmussen Nowadays, I typically focus on JavaScript-intensive projects, React frontend work, accessibility, Node.js backends, ElasticSearch, and automation. I'm also proficient in documentation and technical writing.

A guide to the min(), max(), and clamp() CSS functions

16 min read 4613

A Guide to the min(), max(), and clamp90 CSS Functions

I recently had an opportunity to redesign a site, so I thought I would get a handle on the min(), max(), and clamp() CSS functions that are now starting to be widely enough implemented to be useful.

In my estimation, these functions have the potential to revolutionize web layouts, but they can also make CSS much more difficult to reason about if used to their full potential.

What are these functions?

We’ll refer to MDN for their definitions.

  • min(): Lets you set the smallest (most negative) value from a list of comma-separated expressions as the value of a CSS property value
  • max(): Lets you set the largest (most positive) value from a list of comma-separated expressions as the value of a CSS property value
  • clamp(): Clamps a value between an upper and lower bound. clamp() enables selecting a middle value within a range of values between a defined minimum and maximum. It takes three parameters: a minimum value, a preferred value, and a maximum allowed value

Another interesting note is that you can do math inside the functions without using calc. So, instead of min(calc(5vw + 5px), 50px);, you could just do min(5vw + 5px, 50px);. Of course, you could also have done your calculation inside a variable and just used that variable.

One thing that might be confusing is the naming. min() returns the minimum value from its list of values, and max() returns its maximum value, as you might expect. But what would happen if I were to do the following (a truly ridiculous usage for purpose of the example):

width: min(1px,200px,300px);

Of course, 1px is returned, meaning that the output of min function sets the maximum value of that width to be 1px.

As the W3C spec for CSS Values and Units Module Level 4 says:

An occasional point of confusion when using min()/max() is that you use max() to impose a minimum value on something (that is, properties like min-width effectively use max()), and min() to impose a maximum value on something; it’s easy to accidentally reach for the opposite function and try to use min() to add a minimum size. Using clamp() can make the code read more naturally, as the value is nestled between its minimum and maximum.

Here is an example with three divs, two showing min() value outputs and one showing max() value outputs to set the width:

See the Pen
example min, max
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


Each div has as its text content the actual min() or max() function that is being used to set its width, so you can easily see the effect.

We made a custom demo for .
No really. Click here to check it out.

Each of these functions can be used anywhere a length, frequency, angle, time, percentage, number, or integer can be used. Obviously, this means that they can take as an input any of the units that can be used with these types of values. So, for example, you could have something like the following:

transform: rotate(min(45deg, .15turn));

Using these functions in unexpected ways

Of course, it is immediately obvious how you would use these functions in properties like width or functions like calc() (or, if not immediately obvious how to use them, at least immediately obvious that they could be used there). But there are numerous other ways they can be used that might not be readily apparent.

The primary lesson to take from this section is that since these functions work on units, you can take as an input any function that returns a — such as calc() — or use them inside any function that expects a unit. So as our first example, let’s use them in the very-not-obvious HSL function.

Use with the HSL function

HSL stands for hue, saturation, lightness and is a function added in CSS 3. You can refer to this post for a refresher on HSL.

The following example will not appear to be particularly useful at first, but take a look at how you could use min and max inside the HSL function:

hsl(min( 180, 190, 150), max(75%, 50%; 100%), 50%)

This doesn’t give you anything that you couldn’t get by simply doing the calculation in your head and writing:

hsl(150, 100%, 50%)

In this case, you know what all the values would be. CSS variables make these functions much more useful in cases like this.

Using math and HSL with CSS variables

CSS variables can be overwritten by media queries, loading specific CSS in the context of where you are on the site, or having the variable value changed by JavaScript.

Thus, once you can do things like this:

hsl(
  min(
    var(--extreme-hue),
    var(--base-hue)
  ), 
  max(
    var(--base-saturation),
    var(--region-saturation)
  ),
  50%);

It opens up the door to having more meaningful and powerful calculations. (Please note that in the context of a site or application, variable names may be more reflective of usage; here they are just meant to indicate a methodology.)

Here is an example:

See the Pen
Min,Max with HSL
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


And here is an example of overriding the CSS variable value inside a media query:

See the Pen
Min,Max with HSL media query
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


As I said, you can also override the CSS variable with JavaScript. Here’s a silly example changing the variables randomly in a two-second interval:

See the Pen
Min,Max with HSL JavaScript updating
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

Functions that can use min(), max(), and clamp()

As I’ve demonstrated, because of the existence of CSS variables, even the functions you might consider unlikely to benefit from min(), max(), or clamp() can still use them.

So, here is a list of functions in which you could use the math functions, although you might not normally expect to (functions where you might expect to use them, like calc(), have been left out). The list is divided into types of functions for ease of comprehension:

Color functions

These functions will output a filter or work on an image’s color scheme.

Shape functions

These functions will create a shape (an image) or output a transformation:

There are, of course, other functions available, but some of these are not usable in any way I can think of with these functions, no matter how creative you try to be — or, at the time of this writing, they are not supported across the most popular platforms and, thus, are unlikely to be worth using.

Unexpected ways in which these functions don’t work — yet

As noted before, these functions can accept anything that is a number or a calculable value, like the length of a variable, as an input. Furthermore, they can take as inputs such things as variables, where you might have saved the result of a calculation, a number, or another value.

Many different CSS properties have special keywords that in the end will return a value that is a length or a percentage or a number — that is to say, something that would be potentially usable by one of these functions.

As an example, width has the following special keywords:

  • max-content: The intrinsic preferred height or width
  • min-content: The intrinsic minimum height or width (for example, a block with a single letter in it has the intrinsic minimum height or width of that letter)

To quote from the W3C working group on what intrinsic sizing means:

The min-content size of a box in each axis is the size it would have if it was a float given an auto size in that axis (and no minimum or maximum size in that axis) and if its containing block was zero-sized in that axis. (In other words, the minimum size it has when sized as “shrink-to-fit”.)

The max-content size of a box in each axis is the size it would have if it was a float given an auto size in that axis (and no minimum or maximum size in that axis), and if its containing block was infinitely sized in that axis. (In other words, the maximum size it has when sized as “shrink-to-fit”.)

You might assume that these keywords would be usable in a min/max function, especially when you see that such functions as fit-content use them in their definitions to describe how the function is implemented. But currently, there is no specification on how this should be achieved.

I asked the CSS working group for clarification on whether they could be used, but it seems the answer is “not right now.” They would, however, like them to be usable at some point.

A little bit about clamp()

In this next section, we will get into using the clamp() function, although still with some usage of min() and max() where applicable. As such, we should probably expand on clamp(), and the complexities our three functions can create when used together, before proceeding.

Firstly, remember that clamp() takes exactly three properties: a minimal value, a preferred or default value, and the maximum value. So, clamp(MIN, DEFAULT, MAX).

The ease of reasoning about clamp() is a great benefit. With clamp(), you’ll be able to alleviate some of the QA and designer madness that you as a developer may have noticed at times. I mean, of course, the madness of pixel-perfect design.

How clamp() helps with pixel-perfect design

If you search “the problem with pixel-perfect design” in Google or DuckDuckGo, you will find more than enough arguments against it to keep you occupied. But as a developer, you probably already realized that since pixel-perfect design doesn’t really exist. Consider:

  1. Designs will probably not show every possible state the application can be in. For example, what happens with too little data to implement the design?
  2. A design is made with a particular monitor dimension and resolution in mind, and as such, it does not fit all the other monitors that are found in the world.

However, as a developer, you have probably also experienced people who have control over your work demanding fidelity to the design. Generally, this will be because the design was made to their monitor specification. Thus, they can see it does not look right because you have likely treated the design as a collection of percentages that must be balanced in order to look nice on the widest range of monitors.

But with clamp(), and its combination of minimum, default, and maximum values, you can specify a default or maximum (depending on which makes sense) that will match the design expectations.

So, if you run into a stakeholder that requires a design to look right on their computer (even though they won’t readily accept your argument that it works on your computer as a reason why there isn’t a bug), just remember you have a tool that can make this otherwise ridiculous goal possible.

All that being said, please send them some of the arguments against pixel-perfect design, and try to get them to understand that what they want isn’t sensible before accommodating them.

How clamp() is implemented

MDN asserts that “clamp(MIN, VAL, MAX) is resolved as max(MIN, min(VAL, MAX)).”

Remembering our earlier examples of simple min/max divs with the function written in as text, here are two examples of clamp(), one of which illustrates that the implementation of the clamp() function behaves as MDN describes above:

See the Pen
example clamps
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


This, of course, shows us that it is easy enough to make little variants of clamping behavior. For example, we could easily have a usage like max(MIN, min(Variable 1, MAX), min(Variable 2, MAX)), and in the same way (as we’ve noted before), you could have clamp used inside of min or max; min or max used inside clamp; and calc used in any of these places, along with variables that use any of these functions or just basic values.

Finally, of course, we can mix and match units of measure, as you’ve already seen. But let’s assume you have min(200px; 50%; 50vw). Now you are doing a min of three values, one of which is absolute (200px), one of which is determined by the size of the element that is your parent (50%), and one which is absolutely determined based on window size (50vw).

So while clamp(minimal value, default value, maximum value) might be easier to reason about, this benefit can fly straight out the window when you consider how complicated the determination of those three values could actually be, and how many factors you as a developer might have to hold in your head to reason about the code.

All of which implies the possibility of a greater complexity of CSS than heretofore — at the same time as it implies a comparative increase in power. I’m not going to suggest a way around this problem because I think any suggestion would in part be based on the preferred problem-handling methodology of whoever makes the suggestion.

Thus, I can easily envision some people suggesting that the best practice to deal with these complicated situations would be to institute some sort of coding discipline wherein no clamp() function can hold nested functions or calculations in order to keep reasoning about the expected outputs more simple (perhaps enforced by linting rules).

On the other hand, my preferred way of dealing with this kind of complexity would be to write tooling to help analyze what the CSS code is doing (because I dislike giving up power). Still others might want to write extensive testing of the computed style results.

What you can expect to use these functions for

I think we’ve demonstrated that these functions can be used in many places you might not initially expect to use them, simply because many CSS properties take as their inputs some sort of number. But now, I think it’s time to look at how they work in their primary use cases.

Font sizes

I’m going to start with font sizes because these are often what you start with in a site. (At least I do — I like to give titles and main bits of text their sizes and layout before I start moving on to the more complicated blocks in the design.)

With clamp() and a few calculations, it becomes simple to make a nice font-sizing experience.

For example, here we have a couple of classes — a title and a subtitle — and some variables that we use for sizes. By using clamp() on our title, we can specify that the smallest font size for a title will be the default font size of the application. Our preferred size is 4.5vw, but it must never get bigger than the biggest font size of the application.

We specify the same thing for the subTitle; we just use a little bit of calc to decrease the size.

You will also notice I put a clamp on the letter-spacing of the title and subTitle. Same principle: at the smallest, the letter spacing of the title is .1rem; at the biggest, it will be .5rem. If we can fit 1.5vw in the middle, it will be used. The letter-spacing of the subTitle is slightly smaller in all cases than that of the title.

.title {
  text-transform: uppercase;
  font-weight: 800;
  font-size: clamp(var(--default-font-s), 4.5vw, var(--biggest-font-s));
  letter-spacing: clamp(.1rem, 1.5vw, .5rem)
}

.subTitle {
  font-size: clamp( var(--default-font-s) - 2px, 4.5vw - 10px, var(--biggest-font-s) - 5px);
  letter-spacing: clamp(.1rem, 1vw, .4rem)
}

If you look at the example below, you can see it resizes nicely as you make the window bigger and smaller. (There are, of course, some additional properties on these items to make them look nice.)

See the Pen
min, max, clamp _ titles
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


Now take look at this example:

See the Pen
min, max, clamp _ with cards_simple content
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


This adds in a couple more blocks with text and font-size and letter-spacing set. Some of the styles have been moved around to reuse between classes, but there are no real changes to the styling of the title and subTitle, just the addition of a couple other sections. We will be styling these sections in the next part of the article.

Margins and padding

I think this is actually the best use case for these functions. In almost every project I work on, there comes a point at which I wish I could define a minimum or maximum margin (or padding) in the same way I can define a min-width or max-width.

Look at the following demo:

See the Pen
min, max, clamp _ with cards_simple content
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


You’ll notice there is a significant margin for our cards, which is set by the user agent stylesheet of the browser. For example, in the Brave browser, the user agent stylesheet says:

margin-block-start: 0.83em;
margin-block-end: 0.83em;

If you look in other browser settings, you’ll see similar declarations. You will also notice that as it is set to use an em unit, the size will be affected by our local font-size.

Now look at this demo:

See the Pen
min, max, clamp _ negative margins
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


In my resolvedSectionsNav class, I have the following:

margin-top: min(-10px, -3vh );

This is a slightly naughty bit of code for the pedagogical purpose of pointing out that we can use negative numbers in these functions just as easily as we can positive.

Testing your number

There’s also a button that says GetComputedValue. When clicked, it will tell you the current computed value that was used for the margin-top.

As you decrease the height of the window, the margin decreases as well because the em size of the cardTitle is decreasing (remember — it has a positive margin-block-start and margin-block-end related to the font-size).

But note that the div holding our cards moves up (by decreasing its margin-top) less and less until it reaches -10px, at which point it won’t move any further.

Now that the fun example is out of the way, the more serious example is here:

See the Pen
min, max, clamp _ margins
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


I’ve made some changes to the cardTitle font size just to make it look better, and I’ve centered the title. But the main things to note here are the margins and padding.

By setting a margin-bottom based on the viewport with a clamp():

margin-bottom: clamp( 4px, 3.5vh, 1.5rem)

We can achieve a nice responsive behavior where, as you decrease the screen height, the margin between the rows decreases until the minimum spacing of 4px is reached.

I know it can be difficult to see the difference with such small sizes in CodePen (since the viewport is never that big, and we’re checking against the viewport height), so in order to get a better look at how smooth this makes things, maybe zoom out a bit and then change the margin-bottom to the following:

margin-bottom: clamp( 4px, 6.5vh, 5.5rem)

After which we add a padding-left and padding-right on our .info text, and maybe a text-indent.

Here, we’ve reused the same variable for each:

.info {
  padding-left: var(--card-h-pad);
  padding-right: var(--card-h-pad);
  text-indent: calc(var(--card-h-pad) - 2px);
}

And what does the card-h-pad variable contain? A clamp function:

--card-h-pad: clamp( 15px, 10%, 1.5rem);

Widths and heights

Many people get excited when they first see the min() and max() functions because they see them as a way to reduce code size. That is to say, instead of having something like the following:

.roundedCard {
  min-height: 75px;
  max-height: 125px;
  height: 25vh;  
}

They could have the much less verbose:

.roundedCard {
  height: clamp(75px, 25vh, 125px);  
}

And that’s true — they can. But consider that the min-height, max-height, and, of course, min- and max-width properties are still there, and they won’t be going anywhere for a long time (if ever). I personally believe they will never go away because just as you can use these functions in height and width, you can use them in min-height, min-width, max-height, and max-width as well.

Another excitement that min(), max(), and clamp() often engender is the ability to write less complicated code for media queries. Consider the following:

.example {
  min-width: 100px;
  max-width: 200px;
  width: 80vw;
}

@media only screen and (min-width: 700px) {
  .example {
    max-width: 350px;
    width: 40vw;
  }
} 

@media only screen and (min-width: 1250px) {
  .example {
    max-width: unset;
    width: 500px;
  }
}

So on smaller screens, our .example will be no smaller in width than 100px and no larger than 200px, but it will default to 80vw of the screen size.

Assuming the screen size is determined by device width — like with an iPhone, for example — then in practice, the width of the example would probably be 200px. Assuming it is caused by some developer type sitting around and making their screen very small to test something, then we would probably hit our minimum width.

Our medium screen size here is 700px. When our screen hits 700px, the width is 700 * .40, meaning 280px. Of course, as the screen size gets larger, the width of our .example will increase until the screen size hits 875px, at which point we should hit our max-width of 350px for the medium screen size.

When we hit our large screen size, which is 1250px and above, our .example width is 500px exactly.

Note that in the example below, I’ve added overflow-wrap: break-word to show the behavior more easily:

See the Pen
example min-widths
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


Obviously, we would represent it like so:

.example {
  width: clamp(100px, 80vw, 200px);
}

@media only screen and (min-width: 700px) {
  .example {
    width: clamp(280px, 40vw, 350px);
  }
} 

@media only screen and (min-width: 1250px) {
  .example {
    width: 500px;
  }
}

Our downside here is that because of the width requirements, we essentially have to write the new min() size for 700px — which is 700 * .40 — instead of just inheriting the min-width of 100px from before — which didn’t matter because we were already bigger than that min-width. Aside from this, however, I think there is a reasonable gain in clarity.

Examples with clamp():

See the Pen
example clamps 2
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


But going back to our cards layout, let’s say we add the following width on the roundedCard class:

width: clamp(200px, 25vw, 300px);

You can see it here. If you look at it with the HTML display area sized very small, you will see something like this:

Result of the Display Area Sized Small

So, in this case, you can see the benefit of having a min-width: 200px setting apart from your clamp function, as seen here:

Illustrating the min-width of 200px

But remember: we also are in a flex container, which has its own control over how things are sized to fit. So what happens if you set this:

min-width: clamp(200px, 25vw, 300px);

Illustrating How the First Card Takes the max-value

The first card takes the max value, which doesn’t leave enough space for the max value for the rest of the cards, which then take the default clamped value.

Another result that’s difficult to reason about, caused by using the clamp() function in conjunction with flex layout. But of course, we’re looking good in the smaller content area.

Viewing the Smaller Content Area

Things you might need to know or otherwise think about

If you use a preprocessor…

Many people use Sass, or Less, or who knows what other things that, in my personal opinion, are no longer needed now that we have cssnext and PostCSS. Those libraries have their own built-in (less powerful) min() and max() functions. So, if you’re using either function in one of these preprocessors, you’ll need a way to let them know you are using the actual CSS min() or max().

If you don’t, one of two things will happen:

  1. The function will be evaluated at compile time, which is what you probably don’t want.
  2. The whole thing will fail because you are using values in your calculation — like something using vw or vh units, for example — that your preprocessor is just not going to understand.

So, obviously, you need to escape your CSS min() and max(). In Sass, you do this with the unquote function. So, instead of width: min(200px, 50%, 50vw), you would write width: unquote("min(200px, 50%, 50vw)");. In Less, you would do it with the e function. Same thing: width: e("min(200px, 50%, 50vw)");

Regarding SVG…

As SVG 2 gets implemented, many properties that formerly had to be set directly on your SVG element will be settable as CSS properties — and many of those properties take lengths, percentages, etc. Thus, you will be able to manipulate more of these features of your inline SVG with advanced CSS functionalities such as min, max, and clamp, as well as calc and CSS variables.

There are already SVG properties that can be set in CSS, such as stroke-width, that take a length or a percentage, and which will work with these functions. Here’s an example:

See the Pen
svg with max
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.

Resize the render area to see the changes to the circle. Also, for completeness and for comparison’s sake, here’s the same circle using clamp() instead of max():

See the Pen
svg with clamp
by Bryan Rasmussen (@bryanrasmussen)
on CodePen.


Obviously, min() isn’t going to be too interesting to compare here, as it would just give you a stroke-width of 20px.

Testing your implementation…

As noted at the end of the section How clamp() is implemented, one of the ways that people will want to mitigate the complexity of reasoning about these functions is testing them, which I think is an excellent idea in certain cases.

For example, when writing some complicated sizes that will change dynamically based on window size, it can be useful to test the computedStyle of an element against what we expect it to be. I’ve already made a computedStyle example above, but it will be up to you to integrate using the computedStyle in tests written for your particular GUI testing solution.

A conclusion of sorts

I am concluding here not because I feel that we have reached a natural conclusion, but rather because the article has started to reach an overwhelming length. This is understandable; while these functions are seemingly simple, they have many implications that can touch almost every aspect of modern CSS.

These three functions, especially when combined with calc() and variables, have consequences for both the comprehension and power of CSS. They have the potential to change how responsive design is often implemented and will, I expect, affect the design of tools like Zeplin, Invision, and Sketch in the future.

And that’s just in the areas where we expect to see them having an effect; there will undoubtedly be many scenarios wherein somebody clever has used them to implement a color or animation effect that you would not have expected. So we should keep in mind that these functions can be used in such unexpected manners, or be prepared to spend hours wondering where the JavaScript is that is making things work.

Is your frontend hogging your users' CPU?

As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.https://logrocket.com/signup/

LogRocket is like a DVR for web apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

Modernize how you debug web apps — .

Bryan Rasmussen Nowadays, I typically focus on JavaScript-intensive projects, React frontend work, accessibility, Node.js backends, ElasticSearch, and automation. I'm also proficient in documentation and technical writing.

Leave a Reply