Skip to main content

CSS Math Functions: calc(), min(), max(), clamp() and Beyond

CSS math functions let you perform calculations directly in CSS without JavaScript. They are the foundation of modern fluid, responsive design — and AI tools use them constantly. Understanding them means you can validate, correct, and guide AI output with precision.

1. calc() — Basic Arithmetic in CSS

calc() lets you mix different units and perform arithmetic:

/* Basic arithmetic: + - * / */
.c-container { width: calc(100% - 2rem); } /* Subtract fixed from fluid */
.c-sidebar { width: calc(280px + 2vw); } /* Add px and vw */
.c-hero { min-height: calc(100dvh - 70px); } /* Full viewport minus nav */
.c-col { width: calc(33.333% - 1rem); } /* Grid column minus gap */

Rules for calc()

/* MUST have spaces around + and - operators */
width: calc(100% -2rem); /* ❌ Invalid */
width: calc(100% - 2rem); /* ✅ Valid */

/* * and / don't require spaces (but good practice to add them) */
width: calc(100% / 3); /* ✅ Valid */
width: calc(2 * 1.5rem); /* ✅ Valid */

/* Can be nested */
width: calc(calc(100% - 2rem) / 3);
/* Or use custom properties inside calc */
width: calc(100% - var(--gap) * 2);

Practical calc() Patterns

/* Offset: full width minus sidebar */
.c-main-content {
width: calc(100% - var(--sidebar-width) - var(--gap));
}

/* Sticky nav: hero starts exactly below nav */
.c-hero {
padding-top: calc(var(--nav-height) + 2rem);
}

/* Negative margin: pull element outside its container */
.c-full-bleed {
margin-inline: calc(var(--container-padding) * -1);
}

/* CSS Grid: equal columns minus gap */
.o-grid-3 {
grid-template-columns: repeat(3, calc(33.333% - var(--gap) * 0.667));
}

/* Dynamic spacing: scales with viewport */
.l-section {
padding-block: calc(2rem + 4vw);
}

2. min() — Use the Smallest Value

min() returns whichever value is smallest. It's like a max-width but written inline:

/* min(a, b) → returns the SMALLER of a or b */

/* Classic use: fluid width with a maximum */
.o-container { width: min(100%, 1200px); }
/* → On small screens: 100% (100% < 1200px)
→ On large screens: 1200px (1200px < 100%) */
/* This is equivalent to: width: 100%; max-width: 1200px; */

/* Image that never exceeds its natural size */
img { width: min(100%, 600px); }

/* Fluid padding that doesn't get too large */
.c-hero {
padding-inline: min(10vw, 6rem); /* 10% of viewport, max 6rem */
}

/* Combining with other values */
.c-card { width: min(100%, 400px); margin-inline: auto; }

3. max() — Use the Largest Value

max() returns whichever value is largest. Useful for min-width guarantees:

/* max(a, b) → returns the LARGER of a or b */

/* Text never smaller than 16px, can grow with viewport */
font-size: max(1rem, 2vw);
/* → On small screens: 1rem (1rem > 2vw when viewport is small)
→ On large screens: 2vw (2vw > 1rem when viewport is large) */

/* Padding that's always at least 1rem */
padding-inline: max(1rem, 4vw);

/* Touch targets: always at least 44px regardless of content */
.c-btn { min-height: max(44px, 2.75rem); }

/* Prevent zero gap */
gap: max(1rem, 2vw);

4. clamp() — The Most Powerful CSS Function

clamp(minimum, ideal, maximum) constrains a value between a floor and ceiling:

/* clamp(min, ideal, max) */
/* Result = ideal, but never smaller than min, never larger than max */

/* Fluid typography — the modern approach */
font-size: clamp(1rem, 2.5vw, 2rem);
/* → Never smaller than 1rem (small screens)
→ Scales with 2.5% of viewport width
→ Never larger than 2rem (large screens) */

/* Fluid containers */
.o-container {
padding-inline: clamp(1rem, 4vw, 3rem);
/* → Phone: 1rem minimum
→ Tablet: 4% of viewport
→ Desktop: 3rem maximum */
}

/* Fluid section spacing */
.l-section {
padding-block: clamp(3rem, 8vw, 8rem);
}

/* Fluid heading sizes */
h1 { font-size: clamp(2rem, 5vw, 4rem); }
h2 { font-size: clamp(1.5rem, 3.5vw, 3rem); }
h3 { font-size: clamp(1.25rem, 2.5vw, 2rem); }
body { font-size: clamp(1rem, 1.5vw, 1.125rem); }

clamp() Formula for Font Size

Use this formula to calculate clamp() values that transition precisely between two breakpoints:

Formula:
ideal = (max-size - min-size) / (max-viewport - min-viewport) × 100vw
+ (min-size - min-viewport × slope)

Example: 1rem at 320px → 2rem at 1200px
slope = (2 - 1) / (1200 - 320) = 0.00114
ideal = 0.114vw + 0.63rem

font-size: clamp(1rem, 0.63rem + 0.114vw, 2rem);

[!TIP] Use clamp.font-size.app or fluid.style to generate clamp() pairs visually — enter min/max font sizes and viewport widths and get the exact CSS.


5. min(), max(), clamp() Comparison

Function Use When Mental Model
─────────────────────────────────────────────────────────────
min(a, b) You want a MAXIMUM constraint "At most X"
max(a, b) You want a MINIMUM constraint "At least X"
clamp(a,b,c) You want BOTH min and max + fluid "Between X and Y"
calc(a op b) You need ARITHMETIC between values "Calculate this"

Equivalencies:
min(100%, 1200px) ≈ max-width: 1200px; width: 100%;
max(1rem, 3vw) ≈ min-font-size approach
clamp(1rem, 3vw, 2rem) ≈ fluid between min and max

6. Modern CSS Math: round(), mod(), abs()

Newer CSS functions (2023+) with ~90% browser support:

/* round() — round a value to a specific step */
font-size: round(2.4rem, 0.5rem); /* Rounds 2.4rem to nearest 0.5 → 2.5rem */
width: round(33.333%, 1px); /* Round percentage to pixel grid */

/* mod() — remainder after division (like JavaScript %) */
.zebra:nth-child(n) {
background: hsl(calc(mod(var(--index), 4) * 90deg), 80%, 60%);
}

/* abs() — absolute value (removes negative sign) */
margin: abs(var(--offset)); /* Ensure always positive */

/* rem() — remainder with sign matching dividend (different from mod) */
/* Mostly used in complex mathematical calculations */

7. CSS Functions with Custom Properties

Combining math functions with CSS variables is powerful:

:root {
--base-size: 1rem;
--scale: 1.25; /* Major Third scale ratio */
--nav-height: 4.5rem;
--gap: 1.5rem;
--cols: 3;
}

/* Dynamic type scale using calc + custom properties */
--text-sm: calc(var(--base-size) / var(--scale)); /* 0.8rem */
--text-base: var(--base-size); /* 1rem */
--text-lg: calc(var(--base-size) * var(--scale)); /* 1.25rem */
--text-xl: calc(var(--base-size) * var(--scale) * var(--scale)); /* 1.5625rem */

/* Dynamic grid column width */
.o-grid {
--col-width: calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols));
grid-template-columns: repeat(var(--cols), var(--col-width));
}

8. Real-World Patterns

/* ── Pattern: Full viewport hero minus nav ── */
.c-hero {
min-height: calc(100dvh - var(--nav-height));
}

/* ── Pattern: Fluid responsive container ── */
.o-container {
width: min(100% - 2rem, 1200px); /* Shorthand for width + max-width + padding */
margin-inline: auto;
}

/* ── Pattern: Fluid type scale ── */
:root {
--text-sm: clamp(0.8rem, 0.6rem + 0.5vw, 0.875rem);
--text-base: clamp(1rem, 0.85rem + 0.65vw, 1.125rem);
--text-xl: clamp(1.25rem, 1rem + 1.5vw, 2rem);
--text-2xl: clamp(1.5rem, 1.1rem + 2vw, 2.5rem);
--text-hero: clamp(2.5rem, 1.5rem + 5vw, 5rem);
}

/* ── Pattern: Negative margin full-bleed ── */
.c-full-bleed-image {
margin-inline: calc(var(--container-padding) * -1);
width: calc(100% + var(--container-padding) * 2);
}

/* ── Pattern: Gap-aware grid columns ── */
.o-grid-3 {
--gap: 1.5rem;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--gap);
}
/* Note: with CSS Grid fr units, calc for gap is usually not needed */
/* fr accounts for gap automatically */

/* ── Pattern: Fluid spacing scale ── */
:root {
--space-1: clamp(0.25rem, 0.25rem + 0.2vw, 0.375rem);
--space-2: clamp(0.5rem, 0.4rem + 0.4vw, 0.75rem);
--space-4: clamp(1rem, 0.8rem + 0.8vw, 1.5rem);
--space-6: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem);
--space-8: clamp(2rem, 1.5rem + 2.5vw, 4rem);
}

/* ── Pattern: Dynamic sidebar layout ── */
.l-with-sidebar {
--sidebar-width: clamp(200px, 25vw, 320px);
display: grid;
grid-template-columns: var(--sidebar-width) 1fr;
gap: var(--gap);
}

9. AI Prompting with CSS Math

Instruct AI to use fluid sizing:

Use CSS math functions for all sizing:

Font sizes: clamp(min, fluid, max) — never fixed px
Containers: min(100% - 2rem, 1200px) for max-width + padding in one
Spacing: clamp(2rem, 6vw, 6rem) for section padding
Subtraction: calc(100dvh - var(--nav-height)) for height calculations

Do NOT use:
- Fixed px for font-size
- Separate max-width + width declarations (use min() instead)
- JavaScript for responsive sizing that CSS can handle