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:
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.
More great articles from LogRocket:
Don't miss a moment with
The Replay
, a curated newsletter from LogRocket
Learn
how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
Use React's useEffect
to optimize your application's performance
Switch between
multiple versions of Node
Discover
how to use the React children prop with TypeScript
Explore
creating a custom mouse cursor with CSS
Advisory boards aren’t just for executives.
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.
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:
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.
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:
The function will be evaluated at compile time, which is what you probably don’t want.
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.

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