CSS The calc() Function

The calc() function lets you compute a CSS value directly in your stylesheet, mixing units that would otherwise be impossible to combine — like taking the full viewport width and subtracting a fixed sidebar in pixels. Instead of hardcoding a single number, you write a small arithmetic expression and the browser evaluates it during layout. This makes your CSS far more flexible: a single rule can adapt to different screen sizes, font settings, or dynamically changing custom properties, without JavaScript and without a pile of media queries.

Overview / How it works

calc() is a CSS function that returns a single value computed from an arithmetic expression. You can use it almost anywhere a length, number, percentage, angle, time, or frequency is expected: width, margin, font-size, top, grid-template-columns, transform translate values, and more. The expression supports the four basic operators — addition (+), subtraction (-), multiplication (*), and division (/) — and, crucially, it can mix different units in a single expression, something plain CSS values can never do. You cannot write width: 100% - 20px; directly, because % and px are incompatible units outside of calc(), but width: calc(100% - 20px); is perfectly valid.

Under the hood, the browser’s layout engine does not resolve a percentage-based calc() expression once at parse time — it resolves it during layout, whenever the percentage’s reference value (like the containing block’s width) is known or changes. That is why calc() expressions stay responsive: if the containing block resizes, every calc() value that depends on a percentage or a custom property is recomputed automatically, exactly like a plain percentage would be. This also means calc() plays nicely with the box model — a width: calc(100% - 2rem) combined with box-sizing: border-box gives you precise control over an element’s rendered size relative to its parent, without manual pixel math.

calc() expressions can also be nested inside each other, and they interact seamlessly with CSS custom properties (variables), which is why this lesson lives in the "Variables & Functions" section: a variable can hold a raw number or length, and calc() is very often the tool that turns that stored value into something usable in a real property.

Syntax

property: calc(expression);

The general shape is a single function call containing one arithmetic expression:

  • Operators: +, -, *, / are supported, following standard mathematical precedence (multiplication and division happen before addition and subtraction).
  • Whitespace rule: + and - MUST have a space on both sides (calc(100% - 20px), not calc(100%-20px)). * and / do not strictly require spaces, but consistent spacing is good practice.
  • Mixed units: you can add or subtract values with different units (vw, %, px, em, rem, etc.) — the browser converts them to a common unit internally when it resolves the expression.
  • Multiplication/division rule: at least one side of * must be a unitless number; for /, the divisor (right side) must be a unitless number. You cannot divide one length by another length.
  • Nesting: a calc() can contain another calc(), and can reference var() custom properties inside the expression.
  • Parentheses: ordinary math parentheses can be used inside the expression to group sub-expressions.
Piece Meaning
calc() Wraps the whole expression; required, cannot be omitted
100% - 2rem An expression mixing a percentage and a relative length
var(--gap) A custom property can be used as an operand anywhere inside calc()

Examples

Example 1: A sidebar layout with a fixed gap

.main-content {
  width: calc(100% - 280px);
  margin-left: 280px;
  padding: 1rem;
  box-sizing: border-box;
}

Result: The .main-content element fills the remaining horizontal space next to a 280px-wide sidebar. As the browser window is resized, the content area’s width recalculates automatically so it always exactly fills the leftover space, with no gap and no overflow.

This is the classic use case: a percentage (relative to the parent) combined with a fixed pixel value that plain CSS units cannot express on their own. box-sizing: border-box ensures the padding is included inside that computed width rather than added on top of it.

Example 2: Fluid, capped heading size using viewport units

h1 {
  font-size: calc(1.5rem + 2vw);
  line-height: 1.2;
}

Result: The heading’s text size grows and shrinks smoothly as the viewport width changes — larger on wide desktop screens, smaller on narrow phone screens — while never dropping below roughly 1.5rem because of the fixed base term.

Here calc() blends a fixed rem baseline (so text never becomes unreadably small) with a fluid vw term (so it scales with the screen). This pattern predates the newer clamp() function and is still useful when you only need a floor, or want to understand what clamp() does internally, since clamp() is often built from a calc()-style middle expression.

Example 3: calc() combined with custom properties for a themeable spacing scale

:root {
  --spacing-unit: 8px;
  --card-gap: calc(var(--spacing-unit) * 3);
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--card-gap);
  padding: calc(var(--spacing-unit) * 2);
}

.card {
  border: 1px solid #ccc;
  padding: calc(var(--spacing-unit) * 2);
  border-radius: calc(var(--spacing-unit) / 2);
}

Result: A three-column grid of cards appears with 24px of space between cards (3 × 8px), 16px of padding around the grid and inside each card (2 × 8px), and 4px rounded corners (8px ÷ 2) on every card — all derived from one base --spacing-unit variable.

This shows calc()‘s real power alongside custom properties: change --spacing-unit in one place (say, from 8px to 10px for a "roomier" theme) and every value computed from it — gaps, padding, corner radius — updates consistently, because each is a multiple of the same base unit rather than an independently hardcoded number.

How it works step by step

When the rendering engine encounters a calc() expression during layout, it works through it roughly like this:

  • 1. Parse the expression — the browser tokenizes the operators and operands, respecting standard precedence (*// before +/-) and any explicit parentheses.
  • 2. Resolve custom properties — any var(--name) operand is substituted with its current computed value; if that variable itself contains a calc(), it is resolved first (this is how nesting works).
  • 3. Resolve context-dependent units — percentages are resolved against the relevant reference (e.g. the containing block’s width for a horizontal calc(), or the font size of the parent for em); viewport units (vw, vh) are resolved against the current viewport size.
  • 4. Convert to a common unit and compute — once every operand is a concrete length in the same underlying unit, the browser performs the arithmetic and produces a single resolved value.
  • 5. Feed the result into layout — that resolved value is used exactly like a literal value would be: it participates in the box model, flex/grid sizing, and positioning calculations for that element.
  • 6. Re-run on change — if any input changes (window resize affecting %/vw, a custom property being updated via JavaScript or a different selector matching), the browser re-resolves the expression and updates layout, the same way it would for any other dynamic value.

Common Mistakes

Mistake 1: Missing spaces around + and –

.box {
  width: calc(100%-20px);
}

This is invalid and will make the entire width declaration be ignored by the browser. Without spaces, the parser cannot tell whether -20px is a subtraction operator followed by a value, or part of a single negative-length token — so the CSS specification requires whitespace on both sides of + and - inside calc(). The corrected version:

.box {
  width: calc(100% - 20px);
}

Mistake 2: Dividing one length by another length

.box {
  width: calc(100px / 20px);
}

This is invalid: the divisor in a calc() division must be a unitless number, not another length — dividing a length by a length would produce a dimensionless ratio, which calc() does not resolve back into a usable length for a property like width. If the goal is "100px divided into 20px-sized units," express it as a plain number:

.box {
  width: calc(100px / 20);
}

Mistake 3: Forgetting that calc() output can go negative

.badge {
  width: calc(50% - 400px);
}

Why it’s a problem (not a syntax error): this is syntactically valid CSS, but on any container narrower than 800px, the expression resolves to a negative number. Most length properties like width, padding, and margin-* for physical directions clamp a negative calc() result to 0 rather than throwing an error, which can silently produce a collapsed, invisible element instead of the intended layout. Guard against this with max()/min() or by testing at your smallest supported viewport width, e.g. width: max(0px, calc(50% - 400px));.

Best Practices

  • Always put a single space on both sides of + and - inside calc(); it is required by the spec, not just a style preference.
  • Prefer calc() when you need to mix incompatible units (like % and px); if all operands share the same unit, plain arithmetic done ahead of time in the value is simpler and just as readable.
  • Combine calc() with custom properties to build a consistent spacing/sizing scale, so a single base variable propagates through every computed value.
  • Use box-sizing: border-box alongside percentage-minus-pixel calc() widths so padding and border don’t push the element past its intended size.
  • Reach for clamp(min, calc(...), max) instead of a bare calc() when a fluid value (like viewport-based font size) also needs a hard floor and ceiling.
  • Double-check expressions that can go negative at small viewport sizes — wrap them in max(0px, calc(...)) if a negative result would break the layout.
  • Keep expressions readable: use parentheses to group sub-expressions explicitly rather than relying purely on operator precedence.

Practice Exercises

  • Exercise 1: Write a rule for a .content element inside a page that has a fixed 60px header and a fixed 40px footer, both positioned outside the normal flow. Use calc() so .content‘s min-height always fills exactly the remaining vertical viewport space between them.
  • Exercise 2: Define a custom property --base set to 4px at :root. Then write three declarations using calc(var(--base) * n) for a small, medium, and large spacing value (for example 2x, 4x, and 8x the base) and apply them as padding on three different classes.
  • Exercise 3: A three-column layout has two 16px gaps between columns inside a container. Using calc(), write the width for each column so all three columns plus the two gaps exactly fill 100% of the container’s width. (Hint: divide the leftover space, after subtracting both gaps, by 3.)

Summary

  • calc() computes a CSS value from an arithmetic expression at layout time, and it is the only way to mix incompatible units like % and px in a single value.
  • It supports +, -, *, and /, with standard precedence; + and - require spaces on both sides.
  • Percentage and viewport-unit operands are re-resolved whenever their reference value changes, so calc() values stay responsive automatically.
  • calc() can be nested and can reference custom properties via var(), making it the natural companion to a CSS variable-based design system.
  • Division requires a unitless divisor, and multiplication requires at least one unitless operand.
  • Watch for expressions that can resolve negative at small screen sizes — they’re valid CSS but often clamp to zero and silently break layout.