Dynamic Calculations in CSS Using calc()

Image by Chris Liverani.

In Brief

CSS calc() lets you calculate property values directly in your stylesheet by combining different units, such as percentages, pixels, rems, and viewport units. It is particularly useful for responsive layouts, spacing, sizing, and calculations involving CSS custom properties. When using addition or subtraction, remember that the + and - operators require whitespace on both sides.

As a frontend developer, your CSS toolbox isn't complete without a thorough understanding of calc() and how this powerful function allows you to perform dynamic calculations to determine CSS property values directly within the stylesheet.

Although it has been available and wellsupported for quite some time, calc() remains a genuine gamechanger for creating responsive layouts and finetuning designs across different screen widths. Using it to blend units, perform arithmetic operations, and integrate variables (both CSS variables and the likes of Sass variables) opens up a lot of possibilities that would previously have required JavaScript, or a rather different approach to the layout...


Syntax and Capabilities

Fundamentally, the calc() function can use addition (+), subtraction (-), multiplication (*), and division (/) to calculate property values. Its real power comes from the fact that it supports and allows the mixing of different units within the same expression, such as combining percentages with pixels, or using viewport units (e.g., vw and vh) against dynamic units like percentages or fixed units like pixels or ems.

.element {  width: calc(100% - 80px);  height: calc(50vh + 2em);}

In this example, what I'm doing is demonstrating calc()'s ability to mix units and operations. The width combines a percentage of the containing block with a fixed pixel value, whilst the height combines a viewportrelative value with an em value.

Here, .element would be 80px less than the width of its containing block and 2em taller than half of the viewport height.

Operator Spacing Matters

There is one small but important syntax detail worth knowing here. When using addition or subtraction, the + and - operators need whitespace on both sides. For example:

.main-content {  min-height: calc(100vh-var(--header-height));}

Whereas this is valid:

.main-content {  min-height: calc(100vh - var(--header-height));}

This isn't simply a question of formatting or readability. CSS needs to be able to distinguish the binary + and - operators from the signs used as part of numeric values, so whitespace is required around them.

Multiplication and division don't have the same whitespace requirement, although I would still generally include spaces around all of the operators for consistency and readability.


Responsive Design / Layout Adjustments

As you might appreciate, this ability to combine dynamic and static values means that we developers can create layouts that adjust smoothly across different screen sizes, even with fixeddimension elements, and often without the need for extensive media queries.

As a really basic example, we can use calc() in combination with viewport units and min/max values to create a dynamic element that doesn't break when the screen gets either too small or too big:

.article {  width: calc(50vw - 10rem);  min-width: 30rem;  max-width: 75rem;}

Here, calc() dynamically calculates the width of articles based on the viewport width (50vw), whilst subtracting a fixed margin (10rem), which makes sure that the content doesn't touch the edges of the screen.

The min-width and max-width properties make sure that our article width doesn't become too narrow or too wide, which would help with readability and maintaining design structure across different devices.


Interoperating with Custom Properties

Where calc() becomes even more powerful is when it is used in conjunction with custom properties (either CSS variables or if you're using a CSS processor Sass or Less variables, etc). I've talked about using Sass variables inside calc() before (it's not quite as simple as you might think), so today let's focus on CSS variables instead.

Here's a quick example:

:root {  --header-height: 20rem;}.main-content {  min-height: calc(100vh - var(--header-height));}

In this example, we're setting the custom variable --header-height, and then using calc() to ensure that the main content of the page is always at a minimum the height of the screen (minus the header).

This may not look like much when the two are quite literally sidebyside in the code block above, but if you imagine a much bigger application where different elements need to react to this same dimension, you can hopefully appreciate the power available here. If you combine the variable with JavaScript (i.e., if the header height actually changes based on scroll position, for example), then it's even more powerful still. It also means that should you decide to update your designs and change the height of your header, then it's a single variable change in :root.


Browser Support and Considerations

Whilst it's fair to say that calc() enjoys pretty comprehensive modern browser support without much in the way of browserspecific caveats, it is still worth keeping calculations reasonably easy to follow. Mixing units is one of calc()'s strengths, but deeply nested or unnecessarily complicated expressions can quickly become difficult to reason around and maintain.

The browser is perfectly capable of doing the calculation; the more practical concern is making sure that the expression remains understandable to the next developer who has to change it.


Have a complex web platform issue?

Tell me what is blocked, what has changed, and what needs to be true after the fix. I'll come back with a practical next step.