Articles /

layout

clamp(), min(), max(): CSS math functions

Responsive values that scale smoothly between a minimum and maximum. Replace breakpoints with single-line expressions.

How clamp() works

clamp(MIN, PREFERRED, MAX) returns the preferred value, clamped between the minimum and maximum. It's equivalent to max(MIN, min(PREFERRED, MAX)):

/* Font size: at least 1rem, at most 2rem, scales with viewport */
h1 {
  font-size: clamp(1rem, 4vw, 2rem);
}

/* Padding: at least 1rem, at most 3rem */
.section {
  padding-block: clamp(1rem, 5vw, 3rem);
}

/* Width: fluid but constrained */
.container {
  inline-size: clamp(20rem, 90vw, 65rem);
  margin-inline: auto;
}

The preferred value should include a viewport or container-relative unit so it actually scales. A static preferred value defeats the purpose.

Fluid typography

The most common use of clamp() is fluid type that scales smoothly between two viewport widths without breakpoints. The formula for the preferred value is:

/* Formula: preferred = base + (maxSize - minSize) / (maxVW - minVW) * 100vw
   From 320px viewport (1rem) to 1200px viewport (2.5rem): */

h1 {
  font-size: clamp(1rem, 0.318rem + 1.705vw, 2.5rem);
  line-height: 1.1;
}

h2 {
  font-size: clamp(0.875rem, 0.5rem + 1.2vw, 1.75rem);
  line-height: 1.2;
}

body {
  font-size: clamp(0.9375rem, 0.875rem + 0.25vw, 1.125rem);
  line-height: 1.6;
}
Never use clamp() with only viewport units (e.g., clamp(2vw, 3vw, 4vw)). The min and max values should use absolute units like rem so users can still zoom text.

min() and max()

min() returns the smallest value from a comma-separated list. max() returns the largest. Both accept any number of arguments:

/* min(): take the smaller value — acts as a maximum cap */
.container {
  inline-size: min(90vw, 1200px);
  /* Same as: max-width: 1200px; width: 90vw; */
}

/* max(): take the larger value — acts as a minimum floor */
.sidebar {
  inline-size: max(250px, 25vw);
  /* At least 250px, grows with viewport */
}

/* Multiple arguments */
.element {
  padding: min(2rem, 5vw, 40px);
  /* Whichever is smallest wins */
}

A useful mnemonic: min() sets a ceiling (the value can never exceed the smallest option), while max() sets a floor.

Responsive spacing

Use math functions for spacing that adapts without breakpoints. This creates a more fluid layout than jumping between fixed values at media query boundaries:

:root {
  --space-xs: clamp(0.25rem, 0.5vw, 0.5rem);
  --space-s:  clamp(0.5rem, 1vw, 0.75rem);
  --space-m:  clamp(1rem, 2vw, 1.5rem);
  --space-l:  clamp(1.5rem, 4vw, 3rem);
  --space-xl: clamp(2rem, 6vw, 5rem);
}

.hero { padding-block: var(--space-xl); }
.card { padding: var(--space-m); gap: var(--space-s); }
Define a fluid spacing scale with custom properties at the root level. Every component then pulls from the same adaptive scale, ensuring consistency.

Combining with container units

Math functions work with container query units (cqi, cqb) for component-level responsive sizing that adapts to the container rather than the viewport:

.card-container {
  container-type: inline-size;
}

.card-title {
  font-size: clamp(0.875rem, 3cqi, 1.5rem);
}

.card-body {
  padding: clamp(0.75rem, 4cqi, 2rem);
}

Nesting and arithmetic

Math functions can be nested and combined with calc(). The inner function resolves first:

/* Nested: minimum of two clamped values */
.element {
  inline-size: min(
    clamp(300px, 50vw, 600px),
    calc(100% - 2rem)
  );
}

/* calc() inside clamp() */
.text {
  font-size: clamp(
    1rem,
    calc(0.5rem + 2vw),
    2.5rem
  );
}

Note that calc() is implicit inside clamp(), min(), and max() — you can write clamp(1rem, 0.5rem + 2vw, 2rem) without wrapping the middle value in calc().