# Bound Your clamp()

**SHOULD** · **ID:** `content-impeccable-fluid-type-bounds` · **Category:** content
**Source:** [impeccable](https://impeccable.style/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/content-impeccable-fluid-type-bounds

> SHOULD: Bound every `clamp()`: max-size <= ~2.5x min-size. `clamp(1rem, 5vw, 6rem)` is a 6x span that renders the heading at body size on a phone and shouts at 1400px, and a bare `vw` middle term ignores the reader's font-size preference — add a rem offset (`clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem)`, a 1.7x range) to put zoom and reflow back in the calculation. Second half of the rule: do NOT use fluid type in product UI at all — Material, Polaris, Primer and Carbon all ship fixed rem scales, because dense container-based layouts need spatial predictability. Fluid type is for headings and display text on marketing/content pages; body copy stays fixed even there.

Keep the max at most ~2.5x the min — and keep fluid type out of product UI entirely

This extends `content-fluid-clamp`, which teaches the clamp() technique but sets no bounds on it. The bound is the whole rule: max-size <= ~2.5 x min-size. `clamp(1rem, 5vw, 6rem)` spans 16px to 96px — a 6x ratio — and it is absurd at both ends: at 320px the heading renders at its 16px floor, identical to the body text it is supposed to outrank, and at 1400px it is shouting. A ratio that wide also breaks zoom and reflow, because the viewport unit in the middle term ignores the reader's font-size preference; adding a rem offset (`clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem)` — a 1.7x range) puts that preference back into the calculation. The second half of the rule is the one people miss: do not use fluid type in product UI at all. No major app design system does — Material, Polaris, Primer and Carbon all ship fixed rem scales with optional breakpoint adjustments — because a dense, container-based layout needs spatial predictability, and an h1 that shrinks when the sidebar opens looks worse, not better. Fluid type belongs to headings and display text on marketing and content pages where text dominates the layout. Body copy stays fixed even there, since the size difference across viewports is too small to be worth it.

## Bad — do not do this

`content-impeccable-fluid-type-bounds-bad`

```tsx
/**
 * Bad: `clamp(1rem, 5vw, 6rem)` — a 6x range between the floor and the ceiling.
 *
 * The two boxes below are container-query contexts (`cqw` stands in for `vw` so
 * both ends are visible side by side without resizing the window).
 *
 * - Narrow (320px): 5cqw = 16px, which is the min — the headline is the exact
 *   size of body text. There is no hierarchy at all.
 * - Wide (1100px): 5cqw = 55px — the headline is shouting, four lines of
 *   whitespace where a heading used to be.
 *
 * Same declaration, absurd at both ends.
 */
export function ImpeccableFluidTypeBoundsBad() {
  return (
    <div className="w-full space-y-3">
      <div className="overflow-x-auto">
        <div className="flex gap-3">
          {/* Narrow end */}
          <div className="shrink-0">
            <p className="mb-1 text-xs text-muted-foreground">320px viewport</p>
            <div
              className="rounded-lg border border-error bg-card p-3"
              style={{ containerType: 'inline-size', width: '320px' }}
            >
              <h4
                className="font-semibold text-foreground"
                style={{ fontSize: 'clamp(1rem, 5cqw, 6rem)', lineHeight: 1.2 }}
              >
                Ship faster
              </h4>
              <p className="mt-1 text-[1rem] leading-[1.6] text-muted-foreground">
                Body copy is also 16px. The heading is indistinguishable from it.
              </p>
            </div>
          </div>

          {/* Wide end */}
          <div className="shrink-0">
            <p className="mb-1 text-xs text-muted-foreground">1100px viewport</p>
            <div
              className="rounded-lg border border-error bg-card p-3"
              style={{ containerType: 'inline-size', width: '1100px' }}
            >
              <h4
                className="font-semibold text-foreground"
                style={{ fontSize: 'clamp(1rem, 5cqw, 6rem)', lineHeight: 1.2 }}
              >
                Ship faster
              </h4>
              <p className="mt-1 text-[1rem] leading-[1.6] text-muted-foreground">
                Body copy is still 16px. The heading is now 55px and climbing.
              </p>
            </div>
          </div>
        </div>
      </div>

      <p className="text-xs text-error">
        <code>clamp(1rem, 5vw, 6rem)</code> spans 16px to 96px &mdash; a 6x ratio, more than double
        the ~2.5x ceiling. At the low end the heading collapses into the body text; at the high end
        it dominates the page. A ratio this wide also breaks the browser&rsquo;s zoom and reflow
        behaviour, because the viewport unit ignores the user&rsquo;s font-size preference.
      </p>
    </div>
  );
}
```

## Good — do this

`content-impeccable-fluid-type-bounds-good`

```tsx
/**
 * Good: two answers, picked by surface.
 *
 * 1. Marketing page → bound the clamp. `clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem)`
 *    runs 24px → 40px: a 1.7x ratio, inside the ~2.5x ceiling. The rem offset in
 *    the middle term keeps the user's font-size preference in the calculation,
 *    so zoom and reflow still work.
 * 2. Product UI → do not use fluid type at all. Material, Polaris, Primer and
 *    Carbon all ship fixed rem scales, because a heading that shrinks when the
 *    sidebar opens looks worse, not better.
 *
 * (`cqw` stands in for `vw` below so both ends are visible side by side.)
 */
export function ImpeccableFluidTypeBoundsGood() {
  const bounded = 'clamp(1.5rem, 1.2rem + 1.5cqw, 2.5rem)';

  return (
    <div className="w-full space-y-4">
      <div>
        <p className="mb-1 text-xs font-semibold text-foreground">Marketing page — bounded clamp</p>
        <div className="overflow-x-auto">
          <div className="flex gap-3">
            <div className="shrink-0">
              <p className="mb-1 text-xs text-muted-foreground">320px viewport → 24px</p>
              <div
                className="rounded-lg border border-border bg-card p-3"
                style={{ containerType: 'inline-size', width: '320px' }}
              >
                <h4
                  className="font-semibold text-foreground"
                  style={{ fontSize: bounded, lineHeight: 1.2 }}
                >
                  Ship faster
                </h4>
                <p className="mt-1 text-[1rem] leading-[1.6] text-muted-foreground">
                  Body copy stays fixed at 16px. The heading still outranks it.
                </p>
              </div>
            </div>

            <div className="shrink-0">
              <p className="mb-1 text-xs text-muted-foreground">1100px viewport → 36px</p>
              <div
                className="rounded-lg border border-border bg-card p-3"
                style={{ containerType: 'inline-size', width: '1100px' }}
              >
                <h4
                  className="font-semibold text-foreground"
                  style={{ fontSize: bounded, lineHeight: 1.2 }}
                >
                  Ship faster
                </h4>
                <p className="mt-1 text-[1rem] leading-[1.6] text-muted-foreground">
                  It grew, but it never starts shouting. 24px → 40px is a 1.7x range.
                </p>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div>
        <p className="mb-1 text-xs font-semibold text-foreground">
          Product UI — a fixed rem scale, no clamp
        </p>
        <div className="rounded-lg border border-border bg-card p-3">
          <h4 className="text-[1.25rem] font-semibold leading-[1.3] text-foreground">
            Deployments
          </h4>
          <p className="mt-1 text-[0.875rem] leading-[1.5] text-muted-foreground">
            1.25rem heading / 0.875rem body — same size in a 320px sidebar and on a 2560px monitor.
            Predictable, which is what a dense, container-based layout needs.
          </p>
        </div>
      </div>

      <p className="text-xs text-success">
        Bounded to 1.7x (max &le; ~2.5x min) with a rem offset so zoom keeps working &mdash; and the
        product UI opts out of fluid type entirely, which is what every major design system does.
      </p>
    </div>
  );
}
```

## References

- [impeccable.style](https://impeccable.style/)
- [pbakaus/impeccable](https://github.com/pbakaus/impeccable)
- [MDN — clamp()](https://developer.mozilla.org/en-US/docs/Web/CSS/clamp)
