Responsive Layouts With CSS min(): One Function To Rule Them All?
When Chris Coyier published an experiment in August using container query units for every numeric value in a demo, the result was surprisingly respectable. It highlighted just how complicated responsive sizing can be. CSS gives us absolute units like px, which stay fixed, and relative units like em or vw, which depend on another element’s size. Neither approach is perfect on its own — context matters more than any universal “correct” choice.
In that same spirit, I decided to run my own experiment, but with a different tool: the min() function. Unlike container units, min() accepts any type of length unit, making it far more flexible. My goal was simple — eschew media queries entirely and power an entire layout with min(), leaning heavily on viewport units.
How min() Makes Contextual Decisions
The min() function evaluates two values and applies whichever is smaller in the current context. For instance, an element can be set to 50% of its container, but if that computed value exceeds 200px, the width caps there instead.
See the Pen [[forked]](https://codepen.io/smashingmag/pen/LYwWLMg) by Geoff Graham.
Unlike container query units, min() doesn’t actively measure its container. It simply weighs two supplied lengths and picks the best match for available space. That makes it a natural fit for responsive layouts, essentially embedding conditional logic directly into a property value and removing the need for @media breakpoints.
The difference is subtle but important. A media query triggers at a fixed viewport breakpoint (say, 600px) and swaps between declared values. min(), meanwhile, reacts continuously to the amount of space available. The browser does the calculation for us, and it adapts at every viewport size rather than at discrete thresholds.
That said, pair min() thoughtfully with units. Relative units generally respond better than absolute ones. Within the viewport family, we have width (vw) and height (vh), plus the smarter vmin and vmax units that compare an element’s dimensions and resolve to whichever edge is smaller or larger. Declaring 100vmax on a box that is 500px wide and 250px tall computes to 500px. My experiment uses a combination of these units with min() for every part of a layout.
Fluid Font Sizing Without Media Queries
Responsive typography has historically relied on breakpoints. That approach works, but it struggles on extremes like 4K monitors or foldable phones. Today clamp() is the popular solvent for fluid type, but in keeping with the experiment, I opted for min() alone.
p { font-size: min(6vmin, calc(1rem + 0.23vmax)); }
That single rule replaces an entire block of media queries. Here’s what the arguments mean:
6vminequals roughly 6% of the viewport’s shorter edge, letting the font shrink as needed.- Inside
calc(1rem + 0.23vmax),1remis the base size, and0.23vmaxis a small fraction of the larger viewport edge. - The
calc()sums those two values. Because0.23vmaxresolves differently depending on viewport orientation, the font scales gradually instead of blowing up on large screens. - Finally,
min()picks the smaller of the two arguments for the current screen.
The approach can also cap the upper growth by adding a third parameter:
p { font-size: min(6vmin, calc(1rem + 0.23vmax), 2rem); }
That caps the font-size at 2rem. This works well for body text, but headings may deserve specialized treatment:
h1 { font-size: min(7.5vmin, calc(2rem + 1.2vmax)); }
I raised the vmin base to 7.5vmin to keep headings larger than paragraphs. Inside the calc(), the starting size is 2rem — intentionally below the UA default for an <h1> — and the growth multiplier is 1.2vmax, which outpaces the 0.023vmax used for body copy. Tweak the values to taste; the crucial point is that font sizing is entirely fluid and driven purely by min().
Responsive Space With Margins And Padding
Spacing is essential to layout. You can use pixels, but they stay rigid regardless of screen size. The mixing power of min() comes into play when combining relative and absolute units in one declaration:
div { margin: min(10vmin, 30px); }
On small viewports, 10vmin is likely smaller than 30px, keeping the margin tight. As the viewport widens, once 10vmin passes 30px, min() settles on 30px, preventing excess spacing that might look awkward on larger screens.
This time I avoided calc() — margins don’t need to expand indefinitely. Instead, I recommend letting em do the heavy lifting for subtle scaling, since it’s relative to the element’s own font-size:
.card-info {
font-size: min(6vmin, calc(1rem + 0.12vmax));
padding: 1.2em;
}
By tying spacing to the element’s type size, the padding inherits the behavior of the min()-powered font-size without additional declarations.
Managing Widths With Built-in Limits
Widths are straightforward to make responsive — a single percentage often does the trick. But with min(), you can also set an upper bound. An element can be set to 100% available width yet settle at 650px when there’s more space to spare:
.container { width: min(100%, 650px); }
Text width introduces its own concern: reading comfort. Most readers find 50–75 characters per line ideal. The ch unit solves this neatly, as it’s sized to the 0 glyph of the current font:
p {
width: min(100%, 75ch);
}
That effectively says: fill the available space, but never exceed 75 characters.
From Recipes To A Complete Layout
Once I played around repeatedly with values, I settled on a set of reusable min()-based rules for different sizing jobs:
:root {
--font-size-6x: min(7.5vmin, calc(2rem + 1.2vmax));
--font-size-5x: min(6.5vmin, calc(1.1rem + 1.2vmax));
--font-size-4x: min(4vmin, calc(0.8rem + 1.2vmax));
--font-size-3x: min(6vmin, calc(1rem + 0.12vmax));
--font-size-2x: min(4vmin, calc(0.85rem + 0.12vmax));
--font-size-1x: min(2vmin, calc(0.65rem + 0.12vmax));
--width-2x: min(100vw, 1300px);
--width-1x: min(100%, 1200px);
--gap-3x: min(5vmin, 1.5rem);
--gap-2x: min(4.5vmin, 1rem);
--size-10x: min(15vmin, 5.5rem);
--size-9x: min(10vmin, 5rem);
--size-8x: min(10vmin, 4rem);
--size-7x: min(10vmin, 3rem);
--size-6x: min(8.5vmin, 2.5rem);
--size-5x: min(8vmin, 2rem);
--size-4x: min(8vmin, 1.5rem);
--size-3x: min(7vmin, 1rem);
--size-2x: min(5vmin, 1rem);
--size-1x: min(2.5vmin, 0.5rem);
}
Combining them yields the complete experiment — fluid scaling for all text, a container that adapts but never gets overly wide, dynamic spacing, and more, all with zero media queries:
h1 { font-size: var(--font-size-6x); }
.container {
width: var(--width-2x);
margin: var(--size-2x);
}
.card-grid { gap: var(--gap-3x); }
The --size- custom properties are the most flexible of the lot, working for any property that needs room to scale, be it margin, padding, or something else entirely.
Context Over Dogma
So, is min() the ultimate responsive tool? Clearly not — and neither is going all-in on container query units. The ability to scale an entire page fluidly is impressive, but the web doesn’t bend to a “one-size-fits-all” approach.
Both my experiment and Chris Coyier’s serve as reminders against dogmatic web design practices. Length units and CSS functions are varied tools, each suited to specific jobs. Treating any single technique as the universal answer limits you as a designer. Maybe the best strategy isn’t picking that one perfect hammer — it’s understanding that your shed contains far more than a hammer in the first place.



