Keep Math in the CSS

Avatar of Chris Coyier
Chris Coyier on (Updated on )

There is a sentiment that leaving math calculations in your CSS is a good idea that I agree with. This is for math that you could calculate at authoring time, but specifically chose not to. For instance, if you needed a 7-column float-based grid (don’t ask), it’s cleaner and more intuitive:

.col {
  /* groan */
  width: 14.2857142857%;

  /* oh, I get it */
  width: calc(100% / 7);
}

You could probably prove that the calc() takes the computer 0.0000001% longer, so explicitly defining the width is technically faster for performance reason — but that is about the equivalent of not using punctuation in sentences because it saves HTML weight.

That math can be a little more complicated as you continue. For example, like in our use cases for calc() article, what about columns in that 7-column grid that span?

.column-1-7 {
   width: calc(100% / 7);
}

.column-2-7 {
   width: calc(100% / 7 * 2);
}

.column-3-7 {
   width: calc(100% / 7 * 3);
}

I’d say that’s rather clean to read and manage.

The readability of the math can be enhanced by comments if it gets too complicated. Say you are trying to account for a margin-based gutter with padding inside of an element:

.parent {
  width: 600px;
  padding: 18px;
}

.left {
  /* base width - 1/2 horizontal padding */
  width: calc(400px - 18px);
  margin-right: 1rem; /* gutter */
}

.right {
  /* base width - 1/2 horizontal padding - gutter */
  width: calc(200px - 1rem - 18px);
}

Again, I’d say that’s pretty readable, but it’s also a good amount of repetition. This might call for using variables. We’ll do it with CSS custom properties for fun. You have to pick what is worthy of a variable and what isn’t. You might need fewer comments as the code becomes somewhat self-documenting:

.parent {
  --padding: 18px;
  --gutter: 1rem;
  
  width: 600px;
  padding: var(--padding);
}

.left {
  width: calc(400px - var(--padding));
  margin-right: var(--gutter);
}

.right {
  width: calc(200px - var(--gutter) - var(--padding));
}

That is a decent balance to me. Here’s a step further:

.parent {
  --padding: 18px;
  --gutter: 1rem;
  --parentWidth: 600px;
  --leftSize: 2/3;
  --rightSize: 1/3;
  
  width: var(--parentWidth);
  padding: var(--padding);
}

.left {
  width: calc(calc(var(--parentWidth) * var(--leftSize)) - var(--padding));
  margin-right: var(--gutter);
}

.right {
  width: calc(calc(var(--parentWidth) * var(--rightSize)) - var(--gutter) - var(--padding));
}

Every single number has been given a variable in there. Too far? Maybe. It certainly makes those width declarations pretty hard to wrap your head around quickly. Ana Tudor does some serious stuff with calc(), as proof that everyone’s comfort level with this stuff is different.

One of the things that made me think of all this is a recent article from James Nash — “Hardcore CSS calc()” — where he builds this:

While the solution took a heavily math-y road to get there, it ends up being only sort of medium-level calculation on the ol’ complexity meter. And note that not everything gets a variable’ only the most re-used bits:

See the Pen Fluid 1 + 2 thumbnail block by James Nash (@cirrus) on CodePen.