What min(), max(), and clamp() are
These three CSS functions help you write responsive values without a pile of media queries. They are “comparators”:
-
min(a, b, c...)picks the smallest result. -
max(a, b, c...)picks the largest result. -
clamp(min, preferred, max)picks the preferred value, but never smaller thanminand never larger thanmax.
You can use them anywhere a length/size makes sense: width, font-size, padding,
gap, border-radius, and more.
The mental model
-
min(): “I want it fluid, but don’t let it get too big.” -
max(): “I want it fluid, but don’t let it get too small.” -
clamp(): “I want it fluid in the middle, but with a minimum and a maximum.”
min() basics
min() evaluates each option and chooses the smallest computed value.
A super common use is adding a “cap” to a fluid size.
Cap a fluid width
This pattern is everywhere:
width: min(90vw, 520px).
It means: “Use 90vw, but never exceed 520px.”
.card {
width: min(90vw, 520px);
}
.card {
width: min(100%, 34rem);
}
.card {
width: min(600px, 100%);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
min-height: 240px;
display: grid;
place-items: center;
padding: 20px;
font-family: system-ui, Arial, sans-serif;
}
.card {
padding: 18px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.card h4 {
margin: 0 0 10px;
font-size: 18px;
}
.card p {
margin: 0;
line-height: 1.4;
opacity: 0.9;
}
min() capped card
Resize the preview. The card grows with the viewport, but it stops at a maximum width.
Notice how the last snippet min(600px, 100%) is basically “never overflow the container.”
That can be handy inside narrow layouts.
min() for responsive padding (with a maximum)
You can also cap spacing so it doesn’t become absurd on very large screens.
Here, the padding grows with vw, but never exceeds a fixed size.
.card {
padding: min(4vw, 28px);
}
.card {
padding: min(6vw, 40px);
}
.card {
padding: min(2vw, 18px);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
display: grid;
place-items: center;
padding: 20px;
font-family: system-ui, Arial, sans-serif;
}
.card {
width: min(92vw, 560px);
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.card h4 {
margin: 0 0 10px;
font-size: 18px;
}
.card p {
margin: 0;
line-height: 1.5;
}
Padding that knows when to stop
With min(), padding can grow with the viewport, but it has a built-in “nope, that’s enough” limit.
max() basics
max() chooses the largest computed value. It’s great for enforcing a minimum “floor.”
Set a minimum padding
padding: max(16px, 3vw) means: “Use 3vw, but never go below 16px.”
On small screens, you still get comfortable padding.
.card {
padding: max(16px, 3vw);
}
.card {
padding: max(12px, 2vw);
}
.card {
padding: max(20px, 4vw);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
display: grid;
place-items: center;
padding: 20px;
font-family: system-ui, Arial, sans-serif;
}
.card {
width: min(92vw, 560px);
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.card h4 {
margin: 0 0 10px;
font-size: 18px;
}
.card p {
margin: 0;
line-height: 1.5;
}
Padding with a floor
max() makes sure the padding never becomes too tiny, even when the viewport is small.
max() for minimum readable font-size
This is a way to prevent text from shrinking too far:
font-size: max(16px, 1.8vw).
.demo {
font-size: max(16px, 1.8vw);
}
.demo {
font-size: max(14px, 2.2vw);
}
.demo {
font-size: max(18px, 0.4vw);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
padding: 20px;
display: grid;
gap: 12px;
place-items: center;
font-family: system-ui, Arial, sans-serif;
}
.demo {
width: min(92vw, 720px);
padding: 18px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
line-height: 1.4;
}
.demo strong {
font-weight: 700;
}
Readability test: this text refuses to become microscopic, even when the viewport gets small.
Mixing units and calc()
A big reason these functions are useful is that you can compare different unit types.
For example, vw is responsive to the viewport, while px and rem are more stable.
min() + calc() for smoother growth
A common trick is to add a baseline and then grow from there. Here we say: “Grow with the viewport, but cap it.”
.title {
font-size: min(48px, calc(18px + 3vw));
}
.title {
font-size: min(56px, calc(16px + 4vw));
}
.title {
font-size: min(44px, calc(20px + 2.6vw));
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
padding: 20px;
font-family: system-ui, Arial, sans-serif;
}
.panel {
width: min(92vw, 540px);
margin: 0 auto;
padding: 20px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.title {
margin: 0 0 10px;
line-height: 1.1;
}
.panel p {
margin: 0;
line-height: 1.6;
opacity: 0.9;
}
Fluid title with a cap
The title grows as the viewport grows, but min() prevents it from turning into a billboard.
Nesting min() and max()
You can nest them, but keep it readable. This pattern is basically “clamp without clamp”:
max(minValue, min(fluidValue, maxValue)).
It works, but clamp() is usually clearer.
.box {
width: max(260px, min(80vw, 680px));
}
.box {
width: max(320px, min(90vw, 740px));
}
.box {
width: max(240px, min(70vw, 620px));
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
padding: 20px;
display: grid;
place-items: center;
font-family: system-ui, Arial, sans-serif;
}
.box {
padding: 18px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.box p {
margin: 0;
line-height: 1.5;
}
This box has a minimum width, a fluid middle, and a maximum width. It’s basically clamp(), but expressed with max(min()) nesting.
clamp() basics
clamp(min, preferred, max) is the “sweet spot” function.
It gives you a fluid preferred value that can grow and shrink, but it stays within limits.
Syntax and meaning
- Min: the smallest allowed value.
-
Preferred: usually a fluid expression (often
vworcalc()). - Max: the largest allowed value.
Think of it like a responsible friend: it lets your design be flexible, but it won’t let it do something embarrassing.
Clamp a fluid font-size
Here are a few common clamp styles. In the snippets below, the middle value is a viewport-based size.
.title {
font-size: clamp(1.3rem, 3vw, 2.4rem);
}
.title {
font-size: clamp(1.1rem, 2.4vw, 2rem);
}
.title {
font-size: clamp(1.5rem, 3.6vw, 2.8rem);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
padding: 20px;
font-family: system-ui, Arial, sans-serif;
}
.panel {
width: min(92vw, 540px);
margin: 0 auto;
padding: 20px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.title {
margin: 0 0 10px;
line-height: 1.1;
}
.panel p {
margin: 0;
line-height: 1.6;
opacity: 0.9;
}
Clamp me, I’m responsive
Resize the preview. The title grows and shrinks, but it stays between the min and max.
These are for demonstration purposes only, in the real world, you would use a fluid typography and fluid spacing generator such as Fluid type scale calculator.
clamp() with live sliders
Let’s make clamp() feel real by controlling its three parts.
The sliders below edit the three values inside font-size: clamp(...).
A tiny detail: each slider uses the same name (font-size), so they update the 1st, 2nd, and 3rd value
inside the same declaration, in order.
.headline {
font-size: clamp(1.2rem, 3vw, 2.6rem);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
padding: 20px;
display: grid;
gap: 12px;
place-items: center;
font-family: system-ui, Arial, sans-serif;
}
.card {
width: min(92vw, 540px);
padding: 20px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.headline {
margin: 0 0 10px;
line-height: 1.05;
}
.card p {
margin: 0;
line-height: 1.6;
opacity: 0.9;
}
Slide the clamp
Try setting a high min, a low max, or a tiny preferred value. You’ll quickly see why the order matters: min, then preferred, then max.
The order matters (a lot)
clamp() expects clamp(min, preferred, max).
If your min is larger than your max, the browser has to resolve the conflict.
In practice, you’ll get a value that doesn’t behave like you intended.
Real-world recipes
Recipe: fluid typography that stays readable
A simple beginner-friendly approach:
-
Set a comfortable minimum in
rem. -
Use a viewport-based middle value (
vworcalc()). - Set a maximum so it doesn’t become enormous on wide screens.
.page h2 {
font-size: clamp(1.4rem, 2.6vw, 2.2rem);
}
.page p {
font-size: clamp(1rem, 1.6vw, 1.2rem);
}
.page h2 {
font-size: clamp(1.2rem, 3vw, 2.4rem);
}
.page p {
font-size: clamp(1rem, 1.8vw, 1.25rem);
}
.page h2 {
font-size: clamp(1.5rem, 2.2vw, 2.1rem);
}
.page p {
font-size: clamp(1rem, 1.4vw, 1.15rem);
}
*,
::before,
::after {
box-sizing: border-box;
}
.page {
width: min(92vw, 540px);
margin: 0 auto;
padding: 20px;
font-family: system-ui, Arial, sans-serif;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.page h2 {
margin: 0 0 10px;
line-height: 1.1;
}
.page p {
margin: 0;
line-height: 1.6;
opacity: 0.9;
}
Fluid typography, no drama
This paragraph scales gently with the viewport, but it never gets too tiny or too huge. That’s clamp() doing its job.
Recipe: responsive spacing with clamp()
Spacing is where clamp() feels like a superpower. Your layout breathes on larger screens, but stays tight enough on smaller screens.
.stack {
gap: clamp(10px, 2.5vw, 24px);
}
.card {
padding: clamp(12px, 3vw, 28px);
}
.stack {
gap: clamp(8px, 2vw, 18px);
}
.card {
padding: clamp(10px, 2.5vw, 22px);
}
.stack {
gap: clamp(12px, 3vw, 30px);
}
.card {
padding: clamp(14px, 3.2vw, 34px);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
padding: 20px;
font-family: system-ui, Arial, sans-serif;
}
.stack {
width: min(92vw, 540px);
margin: 0 auto;
display: grid;
}
.card {
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.card h4 {
margin: 0 0 8px;
font-size: 18px;
}
.card p {
margin: 0;
line-height: 1.5;
opacity: 0.9;
}
Card A
Gap and padding scale, but within limits.
Card B
No media queries were harmed in the making of this layout.
Card C
Clamp keeps spacing from getting too tight or too loose.
Recipe: responsive card width (min + max vs clamp)
These two approaches are conceptually similar:
-
Nesting:
width: max(minWidth, min(fluidWidth, maxWidth)) -
Clamp:
width: clamp(minWidth, fluidWidth, maxWidth)
In many cases, clamp() reads better.
.card {
width: max(280px, min(90vw, 640px));
}
.card {
width: clamp(280px, 90vw, 640px);
}
.card {
width: clamp(320px, 80vw, 720px);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
padding: 20px;
display: grid;
place-items: center;
font-family: system-ui, Arial, sans-serif;
}
.card {
padding: 18px;
border: 3px solid #111;
border-radius: 16px;
background: #fff;
}
.card p {
margin: 0;
line-height: 1.5;
}
Resize the preview. Both approaches create a min size, a fluid middle, and a max size. Clamp() is just the “single function” version.
Recipe: responsive images with min() and clamp()
Images often need a max width so they don’t dominate the layout, but you still want them responsive. Here’s a simple pattern: clamp the width and keep the image filling its container.
.media {
width: clamp(220px, 60vw, 520px);
}
.media {
width: min(80vw, 520px);
}
.media {
width: clamp(260px, 50vw, 640px);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
padding: 20px;
display: grid;
place-items: center;
gap: 12px;
font-family: system-ui, Arial, sans-serif;
}
.media {
border: 3px solid #111;
border-radius: 16px;
overflow: hidden;
background: #fff;
}
.media img {
width: 100%;
height: auto;
display: block;
}
.caption {
width: min(92vw, 720px);
opacity: 0.9;
line-height: 1.5;
}
This image wrapper changes size responsively, but it has sensible limits.
Common pitfalls and “why isn’t this working?”
Pitfall: min() is not min-width
min() chooses the smallest computed value.
min-width is a property that enforces a minimum size.
They sound similar, but they solve different problems.
Pitfall: max() is not max-width
Same idea: max() is a function that picks the largest value,
while max-width is a property that caps width.
You can use either approach, depending on what reads clearer for you.
Pitfall: invalid comparisons (the browser can’t compare)
Most of the time, the browser can compare values like px, rem, and vw just fine.
But if you plug these functions into a property that expects something else (like a color),
it will fail and the declaration will be ignored.
Pitfall: wrong order in clamp()
Always remember: clamp(min, preferred, max).
If your values don’t make sense (like a larger min than max), the result will not match your intent.
Debugging checklist
-
Is the property you’re setting expecting a length/size (like
widthorfont-size)? -
Do your values include units where needed (for example,
12vs12px)? -
For
clamp(), is your argument order correct: min, preferred, max? - If it “looks stuck,” try extreme values to confirm which branch is being chosen.
Quick reference cheatsheet
-
Cap something fluid:
width: min(90vw, 520px) -
Give something a floor:
padding: max(16px, 3vw) -
Fluid but bounded:
font-size: clamp(1rem, 3vw, 2.2rem) -
Readable nesting alternative:
max(minVal, min(fluidVal, maxVal))
If you only memorize one thing, make it this:
clamp() is usually the cleanest way to do “responsive in the middle, safe on both ends.”
Learn even more about clamp() in the CSS Clamp Interactive Tutorial.
Are you looking for a minmax() in CSS Grid? Check out the CSS Minmax() Interactive Tutorial.
