w3tweaks.com · CSS Tutorial

CSS calc() Explained

Mix any units, solve layout math, and skip JavaScript entirely.

Tab 1

Build a calc() Expression Live

Pick values and units, choose an operator. The expression builds in real time and the result bar shows the computed width. This is the power of calc() — mixing units the browser can't add any other way.

Expression Builder
width: calc(100% - 32px); ← computed in the browser
100% − 32px
The key insight: You cannot write 100% - 32px in plain CSS — the browser doesn't know what 100% minus 32 pixels equals until layout time. calc() defers the calculation to the browser, which solves it at render time when both values are known.
5 Patterns You'll Use Every Week
Full width minus fixed gap
calc(100% - 32px)
Element fills container but leaves a fixed 32px gap — great for inner content respecting padding.
Two equal columns with gap
calc(50% - 8px)
Each column is 50% minus half the gap — columns perfectly fill the row.
Full viewport minus header
calc(100vh - 60px)
60px header
calc(100vh - 60px) content
Content fills exactly the remaining screen height — no overflow, no gaps.
Fluid font size
calc(1rem + 2vw)
Aa
Font grows with the viewport — resize the window to see it change. Predecessor to clamp().
CSS variable math
calc(var(--spacing) * 2)
--spacing: 8px
calc(var(--spacing) * 2) = 16px
Derive spacing values mathematically from a single token — change one variable, everything updates.
Tab 2

Mixing Units — What's Allowed

The whole point of calc() is mixing units the browser can't combine any other way. But not all combinations are valid. Here's the complete rulebook.

✅ Valid — different unit types
calc(100% - 24px)
100%
24px
% and px are different unit types — browser resolves at layout time when % is known.
✅ Valid — viewport + rem
calc(100vw - 4rem)
100vw
4rem
Viewport units and font-relative units — resolved at different stages but both valid in calc().
❌ Invalid — same types, use plain math
calc(100px + 50px)
Just write 150px
Same unit types can be added before the browser — just write 150px. calc() works but is pointless here.
❌ Invalid — unitless with unit in + / −
calc(100% + 2)
Invalid — use 2px or 2%
Addition and subtraction require both operands to have units. Only multiplication and division can use unitless numbers.
Operation Rule Example Valid?
Addition (+) Both sides need compatible units calc(100% + 20px) Yes
Subtraction (−) Both sides need compatible units calc(100vh - 60px) Yes
Multiplication (×) One side must be unitless calc(var(--base) * 2) Yes
Multiplication (×) Both sides with units calc(20px * 5px) No — gives px²
Division (÷) Right side must be unitless calc(100% / 3) Yes
Division (÷) Dividing by zero calc(100px / 0) No — invalid
Nested calc() calc() inside calc() calc(calc(100% - 20px) / 2) Yes — but use () instead
Spaces around operators matter: calc(100% -32px) is invalid. Always write calc(100% - 32px) with spaces around + and -. The * and / operators don't strictly require spaces, but use them for readability.
Tab 3

Real Use Cases & Why calc() Stops Working

Four patterns you'll reach for every project, plus the three most common reasons calc() silently fails.

📐 Sidebar layout — fixed + fluid

grid-template-columns: 200px calc(100% - 208px) — sidebar is fixed, main area fills the rest minus the gap.

📏 Centered content with full-bleed background
Full-bleed background

margin-inline: calc(50% - 50vw) — breaks an element out of a constrained container to span the full viewport width.

🔢 Equal columns any count

width: calc(100% / 3 - gap) — three equal columns that always sum to exactly 100% regardless of container size.

📌 Sticky offset with variable header
var(--header-height)
16px gap
sticky element lands here

top: calc(var(--header-height) + 16px) — sticky sidebar clears the sticky header automatically when the variable changes.

🔧 calc() Not Working? Here's Why

Missing spaces around + and − operators

This is the #1 reason calc() silently produces no result. The + and - operators must have whitespace on both sides. Without it, the browser misreads the expression — a - without spaces looks like a negative number, not subtraction.

❌ Broken — no spaces
width: calc(100%-32px);
/* browser ignores this */

width: calc(100% -32px);
/* -32px reads as negative */
✅ Fixed — spaces required
width: calc(100% - 32px);
/* space before AND after - */

font-size: calc(1rem + 0.5vw);
/* same rule for + */
Adding a unit to a unitless CSS variable

CSS custom properties store values as-is. If a variable holds a unitless number like --size: 16, you cannot add a unit to it inside calc() by writing calc(var(--size)px) — that's not valid syntax. The unit must be in the variable itself, or multiply by 1px.

❌ Broken
:root {
  --size: 16;
}

width: calc(var(--size)px);
/* invalid syntax */
✅ Fixed — two options
/* Option 1: unit in variable */
--size: 16px;
width: calc(var(--size));

/* Option 2: multiply trick */
--size: 16;
width: calc(var(--size) * 1px);
Using calc() in a property that doesn't accept it

calc() works in any property that accepts a length, percentage, angle, time, or frequency value. It does NOT work in media query conditions, animation steps(), or selector arguments — those require static values.

❌ Broken — inside @media
@media (min-width: calc(768px + 1px)) {
  /* calc() not allowed in
   media query conditions */
}
✅ Use static values in @media
@media (min-width: 769px) {
  /* static value — correct */
}

/* calc() IS fine inside the block */
width: calc(100% - 2rem);
Quick checklist: (1) Spaces around + and -? (2) CSS variables have units included? (3) Not inside a @media condition? (4) Division right side is unitless?
Read the tutorial