Ronalds Vilciņš

Fluid Typography and Spacing With CSS clamp()

CSS clamp() lets a value grow between two limits. For fluid typography, that means a heading can respond to the viewport without becoming tiny on a phone or taking over a wide screen. The same function works for spacing.

The useful part is the boundary. A design rarely needs text that grows forever; it needs a readable range with a sensible transition between its ends.

Set fluid typography limits with clamp()

This heading has a minimum, a preferred calculation, and a maximum:

CSS
h1 {
  font-size: clamp(2rem, 1rem + 4vw, 5rem);
}

The clamp() reference defines those three arguments. When the preferred value is below the minimum, the minimum wins. Above the maximum, the maximum wins; between them, the calculation supplies the size.

The function accepts the calculation directly, so the middle argument does not need an extra calc() wrapper. Keep the minimum below the maximum; reversed limits do not describe the range a reader would normally expect.

At a root font size of 16 pixels, the example resolves to 32 pixels at a 320-pixel viewport. At 800 pixels wide, it reaches 48 pixels; at 1600 pixels, it reaches the 80-pixel cap. Those are calculated examples, not browser measurements.

The exact numbers are a design choice. The same formula can be generous for a short title and much too large for a heading containing a full sentence.

Give the middle value a relative base

A viewport-only declaration has no built-in boundary:

CSS
h1 {
  font-size: 4vw;
}

That ties the entire size to the viewport. In the earlier clamp() example, 1rem supplies a relative base and 4vw contributes the viewport-dependent part.

CSS length units distinguish these references. rem follows the root element’s font size. For font-size, em refers to the inherited font size; for many other properties, it follows the element’s own computed font size.

That distinction matters when building a type scale. A reusable component may want local em spacing, while page typography can share root-relative sizes.

Do not set the root font size to a viewport value merely to make every rem fluid. That changes the reference for much more than the heading, making the rest of the page harder to reason about.

Reuse the range for page spacing

The same three-part decision works for padding:

CSS
:root {
  --page-gutter: clamp(1rem, 5vw, 4rem);
}

.page-section {
  padding-inline: var(--page-gutter);
}

The gutter grows with the viewport while staying between the chosen limits. The custom property keeps that shared decision in one place.

Use separate variables when two values express different decisions. A heading’s size and a page gutter can both be fluid without needing the same multiplier or limits.

A static fallback can precede the declaration when required:

CSS
h1 {
  font-size: 2rem;
  font-size: clamp(2rem, 1rem + 4vw, 5rem);
}

CSS error handling lets an unsupported declaration be ignored while the earlier valid value remains. Choose the fallback for the content, rather than adding a second arbitrary scale.

Check readability between the endpoints

A formula that looks good at the smallest and largest widths can still wrap awkwardly in the middle. Resize through the range and include a long title, rather than judging the scale from one polished phrase.

Text enlargement needs its own check. A rem term is useful, but combining it with viewport units does not automatically guarantee sufficient enlargement under browser zoom. MDN’s accessibility guidance for clamp() calls out the need to allow text to scale.

Avoid fixed-height heading containers that only accommodate the default font size. A larger heading should be allowed to wrap and increase the surrounding layout’s height.

I would use fluid sizing where the intermediate sizes improve the layout. Body copy that is already comfortable at a stable size does not need to move just because the function makes it possible.

Keep layout changes separate from size changes

clamp() describes one value within limits. It does not decide when a sidebar should move below an article or when a navigation row needs another layout.

Media queries still express those structural choices clearly. Use a fluid value for gradual variation, and a breakpoint when the component’s arrangement actually changes.

There is no need to add JavaScript merely to recalculate the heading as the viewport changes. Keep the range in CSS, then spend the attention on whether the resulting text remains comfortable to read.

Filed under on

Last updated on

← Back to all articles