CSS infinity in calc()
October 3, 20266 min read
cssfrontenddesigncalc
CSS can now say "as large as this property allows" without inventing a fake number.
Inside math functions you can write infinity, -infinity, and NaN. A length still needs a unit, so you usually see calc(infinity * 1px). At computed-value time the browser clamps that result to the largest (or smallest) value the property can hold. It is not a literal infinite pixel sitting in the cascade. It is a clear way to ask for the extreme.
Keep a plain fallback above the infinity declaration when older browsers still matter for your audience.
The mess we lived with
We papered over missing language with big numbers:
z-index: 9999until someone used10000width: 99999pxormax-height: 9999pxand hoped nothing grew past itborder-radius: 9999pxfor pills (it works, but it is still a guess)- Expand and collapse transitions on a fake
max-height, so the timing felt wrong clamp()/min()/max()with no honest "no limit" value
We also had no clean way to turn a number into a 0 or 1 inside CSS. Without that, conditional styling from a custom property was awkward.
What you get with infinity
1. Pills without a magic radius
.pill {
border-radius: 9999px; /* fallback */
border-radius: calc(infinity * 1px);
}
The browser clamps to whatever fully rounds the box. No guessing how wide the button might grow.
Old habit
With infinity
2. Always-on-top layers
.overlay {
z-index: 9999; /* fallback */
z-index: calc(infinity);
}
Useful for modals, toasts, and command palettes when you want the top of the stacking context without negotiating against the next "final" overlay.
3. Open-ended clamps
.title {
font-size: clamp(1rem, 2vw, 3rem); /* fallback with a ceiling */
font-size: clamp(1rem, 2vw, calc(infinity * 1px));
}
That reads as: at least 1rem, grow with the viewport, no artificial upper bound. The same idea works for a no-op floor with -infinity.
4. Number to boolean (the useful one)
Multiply a signed difference by infinity, then clamp to 0 and 1:
.card {
--width: 720; /* unitless number you control */
--is-wide: clamp(0, calc(infinity * (var(--width) - 600)), 1);
/* 0 when narrow, 1 when wide */
padding: calc(0.75rem + (var(--is-wide) * 1.25rem));
font-size: calc(0.95rem + (var(--is-wide) * 0.2rem));
}
When --width is below 600, (var(--width) - 600) is negative. Times infinity that goes to -infinity, and clamp returns 0. When it is above 600, you get 1. You can multiply that flag into padding, gaps, or color mixes.
Here is the same idea in a small demo. Change the width token and the flag flips:
Narrow card
--width: 480
--is-wide resolves toward 0
Wide card
--width: 720
--is-wide resolves toward 1
In a supporting browser the wide card gets more padding and a stronger accent on the dot. In an older browser you still get a readable layout from the fallbacks and base styles.
5. Conditional styling without a media query
Once you have a 0/1 flag, you can branch layout values from custom properties. Container size, item count, or a design token can drive the math. You are not replacing every media query. You are covering the cases where the decision already lives in a number CSS can see.
6. Honest design tokens
:root {
--max-size: 80rem; /* fallback */
--max-size: calc(infinity * 1px);
}
--max-size: calc(infinity * 1px) means "unlimited" in a way a teammate can trust. A bare 99999px always looks like a temporary hack that someone forgot to delete.
7. Cleaner utilities
.rounded-full {
border-radius: 9999px;
border-radius: calc(infinity * 1px);
}
.layer-top {
z-index: 9999;
z-index: calc(infinity);
}
One semantic value, less folklore.
Gotchas worth remembering
infinity * 0 is NaN. Multiplying by zero can produce NaN, and different properties treat that differently. Keep the expression away from a hard zero factor unless you mean it.
It does not animate height: auto. That is a different problem. Look at interpolate-size and calc-size() for size interpolation. Using infinity as a giant max-height for expand/collapse is still a poor fit, because the transition still runs across a clamped extreme.
Computed values are clamped. Inherited or computed results reflect the finite clamp, not a mystical infinite length hanging around forever.
Lengths need units. Write calc(infinity * 1px), not bare infinity, when the property expects a length. Unitless z-index: calc(infinity) is fine because z-index is unitless.
Ship fallbacks. Put the ordinary value first, then the infinity line:
.modal {
z-index: 9999;
z-index: calc(infinity);
}
Older engines keep the first declaration. Newer ones take the second.
A small pattern you can reuse
.panel {
/* Inputs */
--count: 3;
--threshold: 2;
/* 1 when count > threshold, else 0 */
--is-crowded: clamp(0, calc(infinity * (var(--count) - var(--threshold))), 1);
gap: calc(0.5rem + (var(--is-crowded) * 0.75rem));
opacity: calc(0.85 + (var(--is-crowded) * 0.15));
}
Feed --count from inline style, a data attribute mapped into a custom property, or your design system. Keep the threshold named. The flag stays readable six months later.
When to reach for it
Use infinity when the old code was apologizing with a huge number: full rounding, top of stack, open clamp, or a boolean flag from a comparison. Skip it when you need a real animation between auto sizes, or when your audience still needs a single declaration that every browser on your support matrix understands.
The feature is small. The win is clarity. CSS finally has a word for "no ceiling" that is not 9999.