Grow your CSS skills. Land your dream job.

A Couple of Use Cases for Calc()

Published by Chris Coyier

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 it's 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 it's 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);
}

Comments

  1. Crispen Smith

    The one thing that I find really counter-intuitive about calc is that 100% seems to always be the same as 100VH (or 100VW) and yet this isn’t generally confirmed in documentation about calc. To me
    .foo {
    width: calc(100%-3em);
    }

    is 100% of the immediate parent, not the window. I fear that calc will continue to be of limited value until we can say calc(#bar(width) -3em); and know that will work.

    • Gray

      I don’t think it’s documented because it’s not true. As long as the parent has a defined width (and your element isn’t fixed) it will always be relative to its parent. Example: http://codepen.io/ggilmore/pen/d8fc7309e1fc387f8965e66674ac031c

    • Crispen Smith

      Thank you.. see, that makes sense and I think it’s the first time it’s made good sense to me. Even as I was writing my original post I was second guessing myself. It’s possible that it’s a case of my own preconceptions, but I still don’t know that I have come across a sentence as simple as your reply. Although, is it “defined width” or “defined relative dimension” (i.e. height: calc(100% – 30px) as per Chris’s original example seems to suggest that would measure height not width.

    • Gray

      Yea, that’s what I meant. Only said width because of the example.

  2. An excellent and informative post…I have been taught calc() to students but have struggled with good use cases…the ones you outlined above are great ideas…thanks for sharing…

    I just wished mobile support was better…but in time that will improve…

  3. Didn’t even know about calc(), wish I had known about this years ago! It’d have saved so much time! Thanks for posting about it!

  4. Solomon

    That is good Chris. I think using the calc will actually not give you the axact size of pages you need cos it will be too large. Is their a way to make the three pages equal.

    Thanks.

  5. Tab Atkins has indicated via Lea Verou’s post on calc() and attr() that the CSSWG have agreed that attr() should be permitted in calc() so now it’s just a matter of time before it’s implemented?

    It would be pretty neat to be able to create simple bar charts based on attribute values without resorting to js.

    Anybody know if there are any performance hits on using calc()? A quick google search yielded little info.

  6. This is really helpful .

    Thanks for Sharing

  7. GreLI

    Well, these cases are doable with older techniques.

    Notably, for #2 you can use keywords which specify from which edge the offset is given, thanks to CSS Backgrounds and Borders Module Level 3:

    background-position: right 50px top 20px;
  8. kudrom

    hi, in the use case #3 you have to substract also the padding of the second area due to the box model; it’d look like this:
    width: calc(60% – 1em – 4 * 30px);
    ciao

  9. Nathan

    Great article as per usual Chris, one question though.

    Regarding backwards compatibility, would you approach your grid replacement example like this?

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

    I know some might say it’s a waste of time (and bytes) but it could be a great way to disambiguate the (almost) magic number. Pointless perhaps?

    • Parker Bennett

      Could also just comment it:

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

  10. John

    Oh wow, I’ve definitely required use case #2 on occasion, every time that situation has come up I’ve completely ignored calc..

    So if I wanted a background image to be centered but off-set… Can you have “calc(centered – 50px)” for example? Or strictly unit values (50% / 50px).

    Will have some playing to do later I think.

  11. M. Ahmad Zafar

    A little help required. Can a sticky footer be created using calc?

    • Connor
      Permalink to comment#

      Theoretically yes, as I understand it, provided the footer’s parent element is equal in height to the page, otherwise your formula would become convoluted.

      But couldn’t you simply use:

      position: absolute; bottom: 0px;
      in relation to the footer div to achieve the same goal?

      My biggest disappointment, having just read this article is the lack of support on the Android, and other mobile browser platforms for ‘calc’.

      I wanted to use calc to relatively position a toggle style div, housing a Login form so that it would be responsive and appear centered along the y-axis.

      All I would then have to do is control its max-width in percentiles for each breakpoint, along with font, line-height, padding and margin parameters through @media queries. I guess I will also have to address the y-axis positioning in the same manner – at least for the time being.

  12. Its like javascript function,mathematics calculation.

  13. Use case # 2 can be achieved without using calc() . To achieve what is done in the demo, you could just do:

    background-position: bottom 20px right 50px;
    

    Demo: http://codepen.io/skimberk1/pen/jdbli

  14. Arpita

    Hii,
    Chris you are Genius you solve problems like a buttering a bread. I try to copy you in the field of designing. and I am doing well. Thank you.

  15. Use case #2 is splendid.

  16. I want to use this feature more, but I’m afraid of the performance cost… Anyone have any numbers on this?

  17. The level of support is higher than I would have initially guessed… Can anyone else see a stage where support and a minimum browser version will be globally adopted to that there is a baseline of support that devs would support and work up?

  18. Well, it is usefull but not big times. It would be nice to mix calc with variables in this way:

    div { position:absolute;top:calc(50%- height / 2) }
    

    or

    li { width: calc ( 100% / counter )  }
    
  19. Nathan

    How difficult would it be to write a polyfill for unsupporting browsers? Would it even be worth it?

  20. DrClue

    If my memory serves , 100% in calc() seems to reflect the width of the last position:relative parent.

    At least that’s what I recall at this particular moment , so don’t take it as the gospel, but rather just something one might want to consider as a possibility. Being as pretty much everything from server boot parameters to CSS and all the coding in between comes across my plate day to day , sometimes it’s hard to keep a precise recollection of it all. :)

    The calc() function was an immediate hit here, although I must confess there were some minor things I wish it could have included such as some conditionals syntax

    As to the attr() value and wide use beyond the content property , one is liable to in those cases run into the peculiarities of instance. When used here (regularly) , what often is required is to add an additional CSS rule that merges the selector of the rule containing the attr() with the selector of a rule uniquely identifying each element in the page the effect is to be applied to.

    The reason is simply that because each instance is likely to have a unique resulting value , each needs a unique slot in the CSS rules to hold the value as opposed to the often sought shared value of classes.

    Folks are regularly running into this instancing behavior as they try to modify animation keyframes in realtime and similar activities , but the cure is always pretty simple once one has it in mind that if a value changes , it needs it’s own rule slot by one means or another.

    Often folks feel they need to re-apply a rule to make it connect but really it just having that separate rule slot in the first place that seems to make the most difference.

  21. Mark

    It’s a really great way to style the height of an element that has the “outline” property. Otherwise the outline slides into nearby elements, unless you specify a margin. Thank you for posting this! It really is great!

  22. Kudos for case #2. This is the best thing we could use it for!

  23. Alan Shortis
    Permalink to comment#

    No. 2 is a huge win.

    My initial thought was that this would be a good alternative to border-box, but as you state, browser compatibility kind of overrides that possibility.

  24. I wanted to use this for vertical centering. I put an image in a fixed height/width wrapper and tried to do something like this:

    margin-top: calc(50% - 75px);
    

    Both FF and Chrome interpret it as -25%. And they do so based on the width of the parent, rather than the height. So, it doesn’t seem to to work for that application.

    Anyone have a better solution using calc()? Seems like it would be a great alternative to adding an extra wrapper and using negative margins.

  25. Richard
    Permalink to comment#

    None of your examples appear to work properly in Firefox, because it still doesn’t support the unprefixed version of the “box-sizing” property. Adding a “-mox-box-sizing: border-box” to your “*” rule fixes the issue.

  26. ElijahFowler
    Permalink to comment#

    I used calc() to set up a responsive grid that has set width gutters, if you change “$siteWidth: 960px;” to what width you want it, the columns will change in width, but the gutters will remain the same. What I’m really looking forward to is using calc() with CSS variable. Imagine changing a grid without having to re-declare it every time with a media query. Just change the variable and the whole grid will change. Kinda sweet.

  27. Christian
    Permalink to comment#

    Here’s a real world example. I’ve got a one-page site I put together for a local honey stand and I kind of use it to experiment with newer CSS features:

    http://beeladieshoney.com/

    The main content DIV uses CSS3 multiple backgrounds (honeycomb pattern, grass [at the bottom], and bees [also at bottom]). To position the bees I use the calc property to calculate all the way down to the bottom then 25 pixels back up. A percentage doesn’t work very well because that will vary as the amount of content varies and because of other factors, and a solid pixel count wouldn’t work either for the same reasons. Still doing some cross-browser testing but I think I have proper fallbacks in place.

    On another note the animation I’ve applied only works in Firefox. Well, it worked in Chrome too with the -webkit- prefix but broke in Safari so I had to take out all -webkit- prefixing, thereby unfortunately also disabling it in Chrome. It should start to work in any browser that updates to not need the prefixes I had been using.

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".