min()
, max()
, and clamp()
CSS functionsI 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.
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 valuemax()
: Lets you set the largest (most positive) value from a list of comma-separated expressions as the value of a CSS property valueclamp()
: 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 valueAnother 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 usemax()
to impose a minimum value on something (that is, properties likemin-width
effectively usemax()
), andmin()
to impose a maximum value on something; it’s easy to accidentally reach for the opposite function and try to usemin()
to add a minimum size. Usingclamp()
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.
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));
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.
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.
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.
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:
These functions will output a filter or work on an image’s color scheme.
drop-shadow()
rgb()
and rgba()
radial-gradient()
radial-gradient
. Other gradient functions will work in the same way, although some gradient functions are not supported across browsersblur()
brightness()
grayscale()
hue-rotate()
opacity()
saturate()
sepia()
contrast()
These functions will create a shape (an image) or output a transformation:
circle()
circle
and the previous radial gradient function. Other shape functions like polygon
, ellipse
, etc. can also use math functions in the same way. To see the effect, resize the render areacubic-bezier
, and other animation functionsinvert()
matrix()
matrix3d()
perspective()
rotate()
(also rotate3d()
, rotateX()
, etc.)scale()
(also scale3d()
, scaleX()
, etc.)skew()
(also skewX()
, skewY()
)steps()
translate()
(also translate3d()
, translateX()
, etc.)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.
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 widthmin-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 anauto
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 anauto
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.
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.
clamp()
helps with pixel-perfect designIf 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:
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.
clamp()
is implementedMDN 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.
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.
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.
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.
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);
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:
So, in this case, you can see the benefit of having a min-width: 200px
setting apart from your clamp
function, as seen here:
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);
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.
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:
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)");
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.
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.
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.
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.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. 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 and mobile apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]