Here is the finished recipe, so you know where this ends up:
That's it. Fluid type, padding in em so it tracks the type, a height floor for thumbs, and a corner radius that scales a little. Now the longer version, because the recipe hides three decisions that are worth understanding.
Why padding in em, not px
Fixed padding looks right at exactly one viewport. padding: 12px 28px on a phone gives a cramped-looking button; on a 1440px monitor the same button looks like a label with delusions of grandeur. The old fix was breakpoint padding. The new fix is fluid padding.
But the unit choice matters more than the fluidity. When the font-size is itself fluid, padding in em scales with the button's own type size, which keeps the visual proportions constant as everything grows. rem padding tracks the root font size instead, so it can drift out of proportion with the button text. px padding never moves at all, and then the type scales away from its own padding, which looks wrong at both ends.
There is one honest trade-off. If your button text wraps to two lines, em padding doesn't save you from a tall button, and the fixed min-height becomes the thing doing the layout work. That is fine. The two properties work together.
The touch target floor comes first
Before any clamp() line, decide the smallest your button is allowed to be. WCAG 2.2 Success Criterion 2.5.8 sets the AA bar at 24 by 24 CSS pixels for interactive targets. The 44px figure you see everywhere is the enhanced AAA criterion, a good aspiration but not the legal floor most teams are held to. I use 44px as a min-height for primary buttons anyway, because thumbs are bigger than the standard and nobody ever complained a button was too easy to tap.
This ordering matters because clamp() sets a minimum, not a guarantee. clamp(0.5rem, ..., ...) guarantees the padding is at least 0.5rem, but the rendered button height depends on font-size, line-height, and border width too. The min-height is the one declaration that enforces the floor directly. Set it, then tune the clamp.
Work the numbers for a real button
Take a primary CTA at a 16px root. On a 320px phone the font-size lands on the 0.95rem minimum (15.2px), the em padding computes to about 11px vertical and 24px horizontal, and the 44px min-height takes over to keep the whole thing tappable. At 768px the font-size reaches roughly 1.02rem (16.3px) with padding near 11px by 26px, so the button breathes without looking inflated. At 1440px the font-size hits its 1.15rem cap (18.4px) with padding around 13px by 29px, a confident CTA rather than a billboard.
The crossover math lives wherever you build the value. Our CSS Clamp Calculator with Tailwind export generates the exact clamp() line from your min and max, and the fluid spacing guide walks through the same technique for padding and gaps generally. If the button sits inside a component that has its own width, container query units beat viewport units for the preferred value.
One more thing you might not have considered, and you are forgiven for skimming past it: buttons that share a row need the same clamp. A primary and secondary button with different fluid values drift apart as the viewport grows, and nothing looks cheaper than two buttons that were clearly sized by two people. One shared .btn base, variants only for color.
Frequently asked questions
What is a good clamp() for button padding?
A solid starting point is padding: clamp(0.5rem, 0.75rem + 0.5vw, 1rem) clamp(1rem, 1.25rem + 1vw, 1.75rem), with a clamped font-size alongside it. Tune the min and max against your type scale, and verify the rendered button never drops below a 24px touch target.
Should button padding be in em or rem inside clamp()?
Both work, but em padding scales with the button's own font-size, so the button keeps its proportions as the text grows. Rem padding stays tied to the root size. For buttons with fluid font-size, em usually looks better.
What is the minimum touch target size for buttons?
WCAG 2.2 Success Criterion 2.5.8 (Target Size Minimum, AA) requires interactive targets to be at least 24 by 24 CSS pixels. The older 44px figure comes from the enhanced AAA criterion 2.5.5. Your clamp() minimum should guarantee at least the 24px floor.
Can I use clamp() for button border-radius too?
Yes. border-radius: clamp(6px, 0.5rem + 0.4vw, 12px) scales the corner curve with the button. Keep it subtle: a radius that grows faster than the button looks melted.
Does fluid button padding work in all browsers?
clamp() is supported in all current browsers, so no fallback is needed for the function itself. If you support very old browsers, declare a fixed padding line above the clamp() line as the fallback.