Most people meet clamp() through one copy-pasted line of fluid typography and never go further. That is a shame, because clamp() is not a typography trick — it is a general-purpose way to say "pick a value between these two limits, and let it scale in between." It works on padding, gaps, border-radius, letter-spacing, custom properties, anything that takes a length. Learn the one function and you delete a stack of media queries.
This post explains the syntax, then gives you 10 interactive demos — each one a small box with a drag handle on the right edge. Drag it and watch the value in the readout change live, both flattening out at its floor and its ceiling and gliding smoothly in between. That flattening is the whole point of clamp(): it is not linear scaling, it is bounded linear scaling.
Dragging the handle is the fast way to see the concept, but it isn't the whole demo. Every embed below has its own HTML / CSS / JS tabs with the full working source, plus a Fork & Edit button that opens that exact snippet — already loaded, already running — in My Code, FWD Tools' free in-browser editor. Change a floor, change a ceiling, swap cqw for vw, break it, fix it. That loop — predict what a number change will do, then actually watch it happen — teaches clamp() faster than reading ten more paragraphs about it ever will. Nothing you edit there touches this page or leaves your browser until you decide to save it.
The syntax, in one breath
property: clamp(MINIMUM, PREFERRED, MAXIMUM);
clamp() takes three comma-separated values and returns whichever one applies:
- If the preferred value would compute below the minimum, the browser uses the minimum instead — the value is floored.
- If the preferred value would compute above the maximum, the browser uses the maximum instead — the value is capped.
- Otherwise, the preferred value is used exactly as computed.
The trick that makes it "fluid" is that the preferred argument is usually a relative unit — vw (a percentage of the viewport width), %, or as in every demo below, cqw (a percentage of a container's width). Because that middle value is relative, it recomputes continuously as the viewport or container resizes, and the min/max simply stop it from ever going too far in either direction. You are looking at three fixed points doing the job of an entire @media block.
Why these demos use cqw, not vw
The classic fluid-typography formula you'll see everywhere is font-size: clamp(1.25rem, 1rem + 2vw, 3rem) — vw ties the preferred value to the real browser viewport. That is exactly right for a real page, but it makes for a broken demo: embedded in an iframe on this page, the "viewport" is the iframe's own width, and you cannot usefully resize an iframe by dragging inside it.
So every demo below uses cqw instead of vw. It is CSS Container Query Units — 1cqw is 1% of the width of the nearest ancestor with container-type: inline-size set on it. Each demo wraps its element in exactly that kind of container, so when you drag the handle and resize that specific box, the clamped value responds to it directly — no need to touch your actual browser window. Everything else about clamp() — the syntax, the floor/ceiling behaviour, the reasoning for using it — is identical whether the preferred value is driven by vw, %, or cqw. Swap cqw for vw in your own project and you get the classic viewport-fluid version; the mechanics you're seeing here don't change.
One caveat worth knowing: -webkit-line-clamp is a different, unrelated CSS property — it truncates multi-line text to a fixed number of lines with an ellipsis. It shares a name with clamp() and nothing else. If you searched for "CSS clamp" hoping to truncate a paragraph, that's the property you want instead.
1. Fluid Heading Typography
The canonical clamp() formula: a rem floor, a rem-plus-relative-unit preferred value, and a rem ceiling. Drag the handle and the headline scales continuously between 20px and 48px.
The rule: font-size: clamp(20px, 6px + 5.2cqw, 48px). The "base + coefficient" shape of the middle argument is what lets you tune two things independently: the flat 6px sets a baseline the relative term always adds to, and 5.2cqw sets how aggressively it grows as the container widens. Drag all the way left and the heading locks at exactly 20px; drag all the way right and it locks at exactly 48px — those flat regions at both ends are clamp() actually clamping, not the demo running out of room.
Best for: hero headlines and page titles, anywhere you'd otherwise write three or four font-size breakpoints. Tip: in production, use vw in place of cqw to tie this to the real viewport instead of a container.
2. Fluid Body Copy
Fluid sizing isn't only for headlines. This paragraph's font-size scales gently between 15px and 19px, which keeps long-form copy comfortable to read whether it's sitting in a narrow sidebar or a wide article column.
The rule: font-size: clamp(15px, 13px + 0.74cqw, 19px). Notice the coefficient here (0.74cqw) is much smaller than the heading's (5.2cqw) — body copy should barely move, while a headline can afford a dramatic range. That's a deliberate design decision every fluid clamp() encodes: how much you want each element to actually respond to available space.
Best for: any paragraph, card description, or long-form text block where you want it to breathe a little more on desktop without ballooning past a comfortable reading size. Tip: keep body-copy ranges tight (a few pixels, like this one) — large swings in paragraph text size read as a bug, not a feature.
3. Fluid Card Padding
clamp() isn't limited to font-size. Here it drives the padding on all four sides of a card at once — cramped padding never happens at the narrow end, and the card never looks like it's swimming in empty space at the wide end.
The rule: padding: clamp(12px, 5cqw, 40px). This is the simplest possible clamp() shape — no additive base term, just a bare relative unit for the preferred value. That's the right call whenever you don't need independent control over the starting point; the floor and ceiling alone are enough to keep the coefficient sensible.
Best for: card components, modal bodies, and any container whose internal spacing should track the container's own size rather than the whole page. Tip: a single clamp() applied to the shorthand padding property sets all four sides identically — if you need different floors per side, write four separate clamp() declarations instead.
4. Fluid Grid Gap
The gap between grid tiles is itself a clamp()-driven value. Drag the handle and watch the tiles pull apart or draw together while staying perfectly aligned — the grid never looks cramped, and it never looks sparse either.
The rule: gap: clamp(6px, 3cqw, 24px). This is often the most-overlooked place to use clamp() — teams will fluidly size their fonts and then leave every gap and margin as a single fixed value, which is exactly what breaks the visual rhythm as the layout scales. One clamp() on gap keeps grid spacing proportional to everything else that's fluidly sized around it.
Best for: any CSS Grid or Flexbox layout where the number of columns is fixed but the available width isn't — product grids, image galleries, dashboard tiles. Tip: pair a fluid gap with grid-template-columns: repeat(auto-fill, minmax(...)) for a grid that adds columns responsively and keeps its spacing proportional.
5. Fluid Border Radius
Small, crisp corner rounding at the narrow end; a soft "squircle" look at the wide end. One clamp() line, no separate rounded/extra-rounded utility classes.
The rule: border-radius: clamp(8px, 3.4cqw, 28px). Border-radius is a property most teams hardcode to one value for the whole design system, but it scales the same way font-size does — a corner that reads as "subtly rounded" on a small card can look almost sharp-edged if that exact pixel value is reused on a much larger hero panel.
Best for: any component that appears at more than one size across your product — buttons, cards, image thumbnails, avatars. Tip: keep the ceiling below half the element's smallest expected dimension, or the corners will visually merge into a pill/circle shape you didn't intend.
6. Fluid Avatar Size
An avatar circle sized entirely with clamp() — both its width/height and its initials' font-size shrink together in a tight sidebar and stay legible in a wide layout.
The rule: width: clamp(32px, 12cqw, 96px) (and the same expression on height, plus a smaller-range clamp() on the initials' font-size). This demo is a good example of coordinated clamp() usage — three separate declarations, each with its own floor and ceiling, all driven by the same container so they move in sync rather than three independent values drifting apart at different rates.
Best for: user avatars, team member photos, or any circular badge that needs to work identically in a cramped comment thread and a spacious profile header. Tip: when width and height share an expression like this, they'll always stay a perfect circle — if you only clamp one of them, you'll get an oval at every size except the exact midpoint.
7. Fluid Hero Vertical Spacing
Vertical padding on a hero block: tight on a small screen so the call-to-action isn't pushed below the fold, generous on a large one so the section doesn't feel squeezed.
The rule: padding-block: clamp(24px, 10cqw, 80px). padding-block is the logical-property version of "top and bottom padding" — using it instead of separately writing padding-top/padding-bottom means this also respects vertical writing modes automatically, at no extra cost.
Best for: hero sections, page headers, and any full-bleed block where vertical rhythm needs to scale with viewport height as much as width. Tip: vertical spacing is the one place teams most often forget clamp() exists — it's usually reserved for font-size, but a fixed 80px of hero padding on a phone is exactly the kind of thing that pushes your headline off-screen.
8. Fluid Letter Spacing
An uppercase eyebrow label, tightly tracked in a narrow space and opened up once there's room to breathe — the kind of detail that separates a label that looks "designed" from one that was just left at its default.
The rule: letter-spacing: clamp(0px, 0.6cqw, 4px). Drag this one and you'll notice the floor never quite goes flat the way the others do — that's not a bug. With a 0px floor and a purely positive coefficient, the preferred value can only equal the floor in the limit as the container shrinks toward zero width, which never happens within a normal drag range. The ceiling, on the other hand, engages exactly as expected. It's a useful reminder to actually check where your own clamp() floors are reachable, not just assume they are.
Best for: eyebrow labels, section kickers, and all-caps microcopy where tracking needs to scale down gracefully rather than staying fixed and looking cramped on mobile. Tip: keep letter-spacing ranges small — a few pixels at most. Large swings read as a font substitution bug before they read as a design choice.
9. Fluid Button Sizing
Horizontal padding on a call-to-action button: comfortable to tap on a small screen, roomy without looking oversized on a large one — and the font-size scales right alongside it.
The rule: padding-inline: clamp(14px, 5cqw, 32px), paired with font: 700 clamp(13px, 3cqw, 16px) system-ui, sans-serif. Two clamp() expressions, one on padding and one on font-size, moving together so the button always looks proportionally sized rather than having its text outgrow its padding (or vice versa) at some in-between width.
Best for: primary CTAs, especially ones that appear in both a compact mobile nav and a spacious desktop hero. Tip: when you clamp two related properties on the same element, make sure their floor-to-ceiling ratios are similar — if one grows 3x and the other only 1.2x, the button's proportions will visibly warp as it scales.
10. Fluid Spacing Scale (Design Tokens)
Every demo so far applied clamp() to one property on one element. This last one shows the pattern that actually scales to a whole design system: three custom properties — --space-sm, --space-md, --space-lg — each its own clamp(), used together to drive an entire spacing scale from three lines of CSS.
The rule:
--space-sm: clamp(6px, 1.8cqw, 14px);
--space-md: clamp(10px, 3.5cqw, 28px);
--space-lg: clamp(16px, 6cqw, 48px);
defined once on a shared ancestor and then referenced everywhere — gap: var(--space-md), padding: var(--space-lg), and so on. This is the version of clamp() worth adopting project-wide: instead of writing a bespoke clamp() expression on every element that needs spacing, you define a small fluid scale once and every component just references a token. Change the scale in one place and the whole product adjusts. One implementation note if you build this yourself: reading a custom property's resolved pixel value in JavaScript requires measuring an element that actually uses it (e.g. getBoundingClientRect().width) — getComputedStyle().getPropertyValue('--space-md') alone returns the literal, unresolved "clamp(...)" text, not the computed number.
Best for: design systems and any codebase with more than a handful of components, where consistent, fluid spacing matters more than any single element's exact padding value. Tip: start with 3-4 tokens (sm/md/lg is often enough) rather than trying to clamp every possible spacing value individually — a scale you can hold in your head is more valuable than one that's technically more precise.
Common clamp() pitfalls
- Argument order matters, and CSS won't warn you.
clamp()expects minimum, preferred, maximum — in that order. Get the order wrong and the property doesn't become invalid or fall back to anything — spec-wise,clamp(MIN, VAL, MAX)is defined asmax(MIN, min(VAL, MAX)), so if your minimum is accidentally larger than your maximum, that formula just returns the minimum every time, no matter what the preferred value or the container width is. The property keeps applying; it just silently stops being fluid. There's no console error to catch this for you — if a clamp()'d value looks frozen, check that the first argument is actually smaller than the third. - vw-only clamps ignore the user's font-size zoom. A visitor who bumps their browser's default font size to make text more readable expects all text to scale, including yours. If your floor and ceiling are in
pxor your preferred value is purevwwith noremcomponent, zooming does nothing. Mixing inremfor the floor/ceiling (as in demo #1) keeps clamp()-driven type responsive to accessibility zoom, not just viewport width. -webkit-line-clampis a different property. It truncates text to N lines with an ellipsis and has nothing to do with theclamp()function covered here — they just happen to share a name.- Container query units need a container.
cqw/cqhonly resolve against an ancestor that hascontainer-type: inline-size(orsize) set. Forget that declaration and the browser has nothing to measure against. - clamp() replaces some media queries, not all of them. It's excellent for continuous scaling of a length value. It can't reorder elements, hide something at a breakpoint, or switch a layout from a grid to a stack — that's still a job for
@media.
Frequently asked questions
What is CSS clamp()?
clamp() is a CSS function that takes a minimum, a preferred, and a maximum value, and returns whichever one actually applies at the current size — it never computes below the minimum or above the maximum, and scales smoothly between them the rest of the time. It works on any property that accepts a length, not just font-size.
What is the syntax of clamp()?
property: clamp(MINIMUM, PREFERRED, MAXIMUM); — three comma-separated values in that exact order. The preferred value is usually written with a relative unit like vw, %, or cqw so it recomputes continuously as the viewport or container resizes, while the minimum and maximum stay fixed.
Can clamp() replace media queries?
For continuously scaling a single length value — a font-size, a padding, a gap — yes, one clamp() line can replace several breakpoints' worth of @media rules. It can't do everything a media query can, though: it won't reorder elements, hide something at a specific width, or switch a grid layout to a stacked one, so it complements @media rather than replacing it outright.
What is the difference between vw and cqw in clamp()?
vw ties the preferred value to the width of the browser viewport; cqw ties it to the width of the nearest ancestor with container-type: inline-size set on it. Use vw for page-level fluid values like a hero headline, and cqw when you want a component to respond to its own container's width rather than the whole page — the same card sitting in a narrow sidebar versus a wide layout, for example.
Can clamp() be used for padding and spacing?
Yes — clamp() works on any length-accepting property, including padding, margin, gap, and border-radius. clamp(12px, 5cqw, 40px) on padding, for instance, keeps a card comfortably spaced at both a narrow and a wide container width, without a single fixed value looking cramped at one extreme or oversized at the other.
Does clamp() work with CSS custom properties?
Yes, and it's one of the more useful patterns: define a custom property like --space-md: clamp(10px, 3.5cqw, 28px) once, then reference it with var(--space-md) across as many components as you like. Change the clamp() expression in one place and every element using that token updates together — the basis of a fluid spacing scale. One caveat: getComputedStyle().getPropertyValue() returns a custom property's literal, unresolved text rather than its computed pixel value, so reading the resolved number back in JavaScript means measuring an element that actually uses it.
Every demo above ships its full HTML, CSS and JS in the HTML / CSS / JS tabs in its own top bar — open any of them, copy the clamp() line you need, and it drops straight into your own project. But the better way to actually learn this is to click Fork & Edit instead of just copying: it hands you the same snippet in a live editor where you can change the numbers and watch the preview update in real time. Retype demo #1's coefficient from 5.2cqw to 1cqw and watch the heading freeze at exactly 20px across the whole drag range — the preferred value can no longer climb high enough to escape the floor. Push demo #3's floor above its ceiling and watch the padding stop scaling entirely, frozen at that floor value no matter how you resize the box. Nudge demo #10's --space-md ceiling and watch both the row spacing and the "md" swatch's width move together, since both are wired to that one token. None of that sinks in from reading — it sinks in from breaking a value and seeing exactly what breaks. Once you've got a version you like, save it to My Code and it's yours to keep, tweak further, or reuse in a real project.
