A Couple of Use Cases for Calc()

Avatar of Chris Coyier
Chris Coyier on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

calc() is a native CSS way to do simple math right in CSS as a replacement for any length value (or pretty much any number value). It has four simple math operators: add (+), subtract (-), multiply (*), and divide (/). Being able to do math in code is nice and a welcome addition to a language that is fairly number heavy.

But is it useful? I’ve strained my brain in the past trying to think of obviously useful cases. There definitely are some though.

Can’t Preprocessors Do Our Math?

All CSS Preprocessors do have math functions and they are pretty useful. But they aren’t quite as powerful as native math. The most useful ability of calc() is its ability to mix units, like percentages and pixels. No Preprocessor will ever be able to do that. It is something that has to happen at render time.

Syntax

.thing {
  width: 90%; /* fallback if needed */
  width: calc(100% - 3em);
}

There must be spaces surrounding the math operator. You can nest.

Browser Support

It is surprisingly good. Can I use… is always great for checking out the details there. On desktop the concerns would be it’s IE 9+, Safari 6+, and won’t be in Opera until it is on Blink in 15+. On mobile, Android and Opera Mini don’t support it at all yet and iOS just on 6.0+.

You’ll have to make the call there. I’ve been able to actually use it in production in certain scenarios already.

Use Case #1: (All The Height – Header)

A block level child element with height: 100% will be as tall as its block level parent element. It can be nice to make a colored module as tall as the parent element in some cases.

But now let’s say the parent element becomes too small to contain all the content in the module. You want the content to scroll, but you want just the content to scroll, not the entire module. Just set overflow-y: auto; right? Not quite, because overflow-y is only useful if the content element itself has a set height that can be overflowed. We can’t make the content element 100% high because with the header there, that will be too high. We need 100% minus the height of the header. If we know that header height, it’s doable!

* {
  /* So 100% means 100% */
  box-sizing: border-box;
}
html, body {
  /* Make the body to be as tall as browser window */
  height: 100%;
  background: #ccc;
  padding: 20px;
}
body {
  padding: 20px;
  background: white;  
}
.area-one {
  /* With the body as tall as the browser window
     this will be too */
  height: 100%;
}
.area-one h2 {
  height: 50px;
  line-height: 50px;
}
.content {
  /* Subtract the header size */
  height: calc(100% - 50px);
  overflow: auto;
}

You might gripe that the header shouldn’t have a fixed size. Might be cool someday if calc() could subtract measured sizes of elements, but that’s not possible yet. You could set the header to overflow with ellipsis.

Check out this Pen!

Use Case #2: X Pixels From Bottom Right Corner

We can position background-image X pixels from the top-left corner easily.

background-image: url(dog.png);
background-position: 50px 20px;

That would put the dog 50px from the left and 20px from the top of the elements box. But what if you want it 50px from the right and 20px from the bottom? Not possible with just straight length values. But calc() makes it possible!

background-image: url(dog.png);
background-position: calc(100% - 50px) calc(100% - 20px);
Check out this Pen!

Use Case #3: Fixed Gutters Without Parents

Let’s say you want two columns next to each other. The first 40% wide, the second 60%, but with a fixed 1em gap between the columns. Don’t Overthink It Grids have fixed gutters, but they aren’t true gutters in a sense. The columns themselves bump right into each other and the columns are made by internal padding inside those columns.

Using calc(), we can make the first column 40% wide with a right margin of 1em, then make the second column 60% wide minus that 1em.

.area-one {
  width: 40%;
  float: left;
  margin-right: 1em;
}

.area-two {
  width: calc(60% - 1em);
  float: right;
}

You could remove half the gutter from both if you wanted to keep the proportion more accurate. Now you have two true columns separated by fixed space without needing parent elements or using the internal padding.

Check out this Pen!

Use Case #4: Showing Math Is Easier To Understand

Speaking of columns, sometimes division math gets messy. Let’s say you wanted a 7-column grid, you might have classes like:

.column-1-7 {
   width: 14.2857%
}
.column-2-7 {
   width: 28.5714%
}
.column-3-7 {
   width: 42.8571%
}

Not exactly magic numbers, but difficult to understand at a glance.

.column-1-7 {
   width: calc(100% / 7);
}
.column-2-7 {
   width: calc(100% / 7 * 2);
}
.column-3-7 {
   width: calc(100% / 7 * 3);
}
Check out this Pen!

Use Case #5: Kinda Crappy box-sizing Replacement

I’m a fan of universal box-sizing: border-box; because it means you don’t have to do much math to figure out how big an element actually is, our adjust that math when things like border and padding change.

If you want to replicate what box-sizing does, you could use calc() to subtract the values as needed.

.module {
  padding: 10px;

  /* Same as box-sizing: padding-box */
  width: calc(40% - 20px);

  border: 2px solid black;

  /* Same as box-sizing: border-box */
  width: calc(40% - 20px - 4px);
}

box-sizing has far better browser support than calc() though, so this would be rarely used.

The Future?

I think it will be interesting when we can use the attr() function in places other than the content property. With that, we could yank the value from HTML elements, run calculations on them, and use the new numbers to do design-y things. Like colorize inputs based on the numbers they contain.

Perhaps we could even use it to do fancy things with the <progress> elements like turn it into a speedometer like on this page. Perhaps something like:

/* Not real */
progress::progress-bar {
  transform: rotate(calc(!parent(attr(value))*18)) + deg);
}