Menu Search
Jump to the content X X
Smashing Conf New York

You know, we use ad-blockers as well. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf New York, dedicated to smart front-end techniques and design patterns.

Getting Started With CSS calc()

I first discovered the calc() function more than four years ago, thanks to CSS3 Click Chart1, and I was absolutely delighted to see that basic mathematical computations — addition, subtraction, multiplication and division — had found their way into CSS.

A lot of people think preprocessors fully cover the realm of logic and computation, but the calc() function can do something that no preprocessor can: mix any kind of units. Preprocessors can only mix units with a fixed relation between them, like angular units, time units, frequency units, resolution units and certain length units.

Further Reading on SmashingMag: Link

1turn is always 360deg, 100grad is always 90deg, and 3.14rad is always 180deg. 1s is always 1000ms, and 1kHz is always 1000Hz. 1in is always 2.54cm or 25.4mm or 96px, and 1dppx is always equivalent to 96dpi. This is why preprocessors are able to convert between them6 and mix them in computations. However, preprocessors cannot resolve how much 1em or 1% or 1vmin or 1ch is in pixels because they lack context.

Let’s look at a few basic examples:

div {
   font-size: calc(3em + 5px);
   padding: calc(1vmax + -1vmin);
   transform: rotate(calc(1turn - 32deg));
   background: hsl(180, calc(2*25%), 65%); 
   line-height: calc(8/3);
   width: calc(23vmin - 2*3rem);

On some occasions, we might want to use variables in the calc() function. This is totally possible with the most popular preprocessors.

First, with Sass7, we have variable interpolation, just as with any other native CSS function:

$a: 4em
height: calc(#{$a} + 7px)

Here is LESS8:

@a: 4em;
height: ~"calc(@{a} + 7px)";

And here is Stylus9:

a = 4em
height: "calc(%s + 7px)" % a

We can also use native CSS variables10, but note that this only works in Firefox 31+ for the moment, because no other browser supports CSS variables yet.

Update: Safari and Chrome/Opera now support CSS variables11 as well and they are listed as “in development” for Edge12.

--a: 4em;
height: calc(var(--a) + 7px);

We need to keep a few things in mind to ensure that the calc() function works. First, division by zero obviously won’t work. Having a space between the function name and the parenthesis is not allowed. And the plus and minus operators must be surrounded by white space. This means that the following are not valid:

calc(50% / 0)
calc (1em + 7px)

The calc() function should work as a value in all places where a number value, with or without specified units, works. However, while basic support is really good, we might run into trouble depending on where we use it. Let’s look at a few examples, including what support problems they have (if any) and whether they’re ultimately the best solution.

Easier To Understand Computed Values Link

Let’s say we want a rainbow gradient. The CSS for this is really simple13:

background: linear-gradient(#f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00);

But those HEX values don’t make much sense. Using hsl() and calc(), although more verbose, makes things a lot clearer:

background: linear-gradient(hsl(calc(0*60), 100%, 50%), 
                            hsl(calc(1*60), 100%, 50%), 
                            hsl(calc(2*60), 100%, 50%), 
                            hsl(calc(3*60), 100%, 50%), 
                            hsl(calc(4*60), 100%, 50%), 
                            hsl(calc(5*60), 100%, 50%), 
                            hsl(calc(6*60), 100%, 50%));

Sadly, using calc() in hsl(), rgb(), hsla() or rgba() doesn’t work at the moment in Firefox or Internet Explorer14 (IE), which means this version works only in WebKit browsers15 for now. So, in practice, it’s still probably better to let a preprocessor handle it all at this point, including the computations. And the best thing about using a preprocessor is that it lets us generate the list in a loop16:

$n: 6;
$l: ();

@for $i from 0 through $n {
   $l: append($l, hsl($i*360/$n, 100%, 50%), comma);

background: linear-gradient($l);

More Efficient Gradient Backgrounds For Flexible Elements Link

Let’s say we want a background with a fixed 1em stripe both at the top and at the bottom. The only problem is we don’t know the height of the element. One solution would be to use two gradients17:

   linear-gradient(#e53b2c 1em, transparent 1em),
   linear-gradient(0deg, #e53b2c 1em, #f9f9f9 1em);

But we’d need only one gradient if we use calc()18:

   linear-gradient(#e53b2c 1em, #f9f9f9 1em, 
                   #f9f9f9 calc(100% - 1em), 
                   #e53b2c calc(100% - 1em));

This should work fine in all browsers that support calc() and gradients, and because it involves mixing units, it isn’t something that preprocessors have an equivalent for. We can, however, make it more maintainable by using variables19:

$s: 1em;
$c: #e53b2c;
$bg: #f9f9f9;

   linear-gradient($c $s, 
                   $bg $s, 
                   $bg calc(100% - #{$s}), 
                   $c calc(100% - #{$s}));

Note: For some reason, one of the stripes appears to be slightly blurry and narrower than the other in Chrome and Opera.

Diagonal Gradient Stripes Link

Let’s say we want an element with a thick diagonal stripe extending on both sides of its actual diagonal. We could do it using percentage-based stops20:

   linear-gradient(to right bottom, 
                   transparent 42%, #000 0, #000 58%, 
                   transparent 0);

In this case, the width of the stripe would depend on the element’s dimensions. Sometimes, that’s exactly what we want. For example, this is how we’d do things if we wanted to reproduce a flag in CSS. Adding a bit of green, yellow and blue to the gradient above gives us a flag21 that fine-chocolate lovers probably recognize — the flag of Tanzania.

   linear-gradient(to right bottom, 
                   #1eb53a 38%, #fcd116 0, 
                   #fcd116 42%, #000 0, 
                   #000 58%, #fcd116 0, 
                   #fcd116 62%, #00a3dd 0);
Flag of Tanzania22
Flag of Tanzania. (View large version23)

But what if we want our diagonal stripe to have a fixed width that doesn’t depend on the element’s dimensions? Well, we’d use calc() and put the stops at 50% minus half of the fixed stripe’s width and at 50% plus half of the fixed stripe’s width. If we want the stripe’s width to be 4em, then we’d have this:

   linear-gradient(to right bottom, 
                   transparent calc(50% - 2em), 
                   #000 0, 
                   #000 calc(50% + 2em), 
                   transparent 0);

You can test this live24 by resizing the window. The element’s dimensions are expressed in viewport units and, therefore, change with the viewport, but the diagonal stripe always stays the same width.

Positioning Children Of Known Dimensions In The Middle Link

You’ve likely seen the little trick25 of absolutely positioning an element dead in the middle of its parent:

position: absolute;
top: 50%; 
left: 50%;
margin: -2em -2.5em;
width: 5em; 
height: 4em;

With calc(), we can get rid of the margin rule26:

position: absolute;
top: calc(50% - 2em); 
left: calc(50% - 2.5em);
width: 5em; 
height: 4em;

And we can make it more maintainable using variables for width and height27:

$w: 5em;
$h: 4em;

position: absolute;
top: calc(50% - #{.5*$h});
left: calc(50% - #{.5*$w});
width: $w; 
height: $h;

Note that, while using offsets (top, left) for initial positioning is safe, if you plan on animating the position of the element afterwards, you should use transforms instead. This is because changing transforms requires only compositing28, whereas changing offsets also triggers a relayout and repaint — thus, impairing performance.

System Of Coordinates And Grid With Origin In The Middle Link

Since discovering the four-value background-position29, I haven’t been too keen on using calc() to position backgrounds relative to the right or bottom side of the element. But calc() turned out to be a great solution for positioning a certain point of the background relative to the middle of the element.

A couple of years ago, I found myself wanting to create a background that represents a system of coordinates with a grid behind and whose origin would be dead in the middle of the screen.

System of coordinates and grid30
System of coordinates and grid. (View large version31)

The system of coordinates and the grid part32 were easy to achieve:

   linear-gradient(#e53b2c .5em, transparent .5em) /* horizontal axis */,
   linear-gradient(90deg, #e53b2c .5em, transparent .5em) /* vertical axis */, 
   linear-gradient(#333 .25em, transparent .25em) /* major horizontal gridline */, 
   linear-gradient(90deg, #333 .25em, transparent .25em) /* major vertical gridline */, 
   linear-gradient(#777 .125em, transparent .125em) /* minor horizontal gridline */, 
   linear-gradient(90deg, #777 .125em, transparent .125em) /* minor vertical gridline */;

   100vw 100vh, 100vw 100vh, 
   10em 10em, 10em 10em, 
   1em 1em, 1em 1em;

But how do we make the origin of the background dead in the middle and not in the top-left corner? First, background-position: 50% 50% won’t work because it makes the 50% 50% point of the gradient coincide with the 50% 50% point of the element, but the lines are at the top and at the left of the gradients, respectively. The solution is to use calc() and position the gradients so that their top left is almost in the middle of the viewport, just offset to the top and left by half the axis or the grid line’s width:

    0 calc(50vh - .25em), calc(50vw - .25em), 
    0 calc(50vh - .125em), calc(50vw - .125em), 
    0 calc(50vh - .0625em), calc(50vw - .0625em);

Again, we can make it all more maintainable by using variables:

See the Pen system of coordinates + grid #233 by Ana Tudor (@thebabydino34) on CodePen35.

Maintaining Aspect Ratio And Covering A Viewport Dimension Link

One thing I’ve always wanted when creating HTML slides was for each slide to be a box of a certain fixed-aspect ratio that always covers at least one dimension of the viewport and that is, of course, in the middle along the other axis.

Proportional box animation

Let’s start by assuming that the desired aspect ratio for the slides is 4:3 and that I’m on a widescreen display. This means the slides cover the viewport vertically but still have some space on the left and the right.

Proportional box: case 136
Proportional box: case 1. (View large version37)

Covering the viewport vertically means a height of 100vh. Knowing the height and the aspect ratio, we can get the width, which is 4/3*100vh. And to get it in the middle, we need to offset it from the left by half the viewport’s width (100vw/2) minus half the slide’s width (4/3*100vh/2). This is where we need the calc() function because we have to mix units.

.slide {
   position: absolute;
   left: calc(100vw/2 - 4/3*100vh/2);
   width: calc(4/3*100vh);
   height: 100vh;

Things change, however, for a display with an aspect ratio that’s less than 4:3. In this case, the slides cover the viewport horizontally, with some space left at the top and the bottom.

Proportional box: case 238
Proportional box: case 2. (View large version39)

Covering the viewport horizontally means a width of 100vw. Knowing this and the aspect ratio will give us the height, which is 3/4*100vw. Finally, the top offset is half the viewport’s height minus half the slide’s height; so, 100vh/2 - 3/4*100vw/2.

@media (max-aspect-ratio: 4/3) {
   .slide {
      top: calc(100vh/2 - 3/4*100vw/2);
      left: auto; /* Undo style set outside media query  */
      width: 100vw;
      height: calc(3/4*100vh);

We can, of course, make things more flexible by not hardcoding the aspect ratio and using two variables instead (one for width and one for height). Here is the Sass version, which you can also test live40 by resizing the window:

$a: 4;
$b: 3;

.slide {
   position: absolute;
   top: 0; 
   left: calc(50vw - #{$a/$b/2*100vh});
   width: $a/$b*100vh; 
   height: 100vh;
   @media (max-aspect-ratio: #{$a}/#{$b}) {
      top: calc(50vh - #{$b/$a/2*100vw}); 
      left: 0;
      width: 100vw; 
      height: $b/$a*100vw;

Even better, we could turn this into a mixin41, which is generally a better practice than using global variables:

@mixin proportional-box($a: 1, $b: $a) {
   position: absolute;
   top: 0; 
   left: calc(50vw - #{$a/$b/2*100vh});
   width: $a/$b*100vh; 
   height: 100vh;
   @media (max-aspect-ratio: #{$a}/#{$b}) {
      top: calc(50vh - #{$b/$a/2*100vw}); left: 0;
      width: 100vw; height: $b/$a*100vw;

.slide {
   @include proportional-box(4, 3);

Note that $a and $b must be integers42 in order for the media query to work.

This is supported in all current versions of major browsers. However, WebKit browsers didn’t support the use of viewport units in the calc() function until recently. This has been fixed in Safari 8 and Chrome 34, respectively, with Opera trailing.

Short Slide Title In The Middle Link

I wanted two more things for slide presentations.

The first was for the slides not to really cover the entire viewport because the edges might get cut off. This was an easy fix. I simply set their box-sizing to border-box and also set a border on them.

The second thing I wanted was to mark sections by starting each with a slide that had nothing but a short and memorable title right in the middle.

Desired result43
Desired result. (View large version44)

I didn’t want to use absolute positioning, so I thought I’d go with setting an appropriate line-height.

In case the slide’s height, including the border, covers the entire height of the viewport, I would have a line-height of 100vh minus twice the slide’s border-width:

$slide-border-width: 5vmin;

.slide {
   /* The other styles */
   box-sizing: border-box;
   border: solid $slide-border-width dimgrey;
   h1 {
      line-height: calc(100vh - #{2*$slide-border-width});

In case the slide, including the borders, covers the viewport horizontally (and was vertically in the middle), its height would be $b/$a*100vw. So, the line-height for the title would be that minus twice the slide’s border-width:

line-height: calc(#{$b/$a*100vw} - #{2*$slide-border-width});

This was my initial idea, which, in theory, should work just fine. And it does in WebKit browsers and IE45. However, it turns out that calc() values don’t work for line-height (and some other properties) in Firefox46 — they work now47; so, calc() is not the best solution there. Luckily, there are a lot of other ways to solve this problem (flexbox, absolute positioning and more).

Fixed Point Of View Link

One thing I enjoy playing with a lot is CSS 3D — creating geometric 3D shapes with CSS in particular. If I create just one shape, then I’d normally position it in the middle of the scene it’s contained in. The scene is the element on which I set the perspective and also the parent of the shape element. The shape element will have its own descendants, which are the shape faces, but we won’t go into detail about them here; if you want to learn how they are positioned, then check out my guest article on CSS-Tricks48.

Setting a perspective on a scene ensures we’ll see everything that is closer as being bigger and everything that is further away as being smaller. The perspective property accepts length values, and the smaller these values are, the greater the contrast49 between what’s closer to us and what’s further away.

Now, let’s say we have a very simple 3D shape — a cube, for example — right in the middle of our scene. It doesn’t look very 3D: It’s way too symmetrical, and if the faces are fully opaque, we can only see the front one.

Cube. (View large version51)

We could rotate it a bit, let’s say by 30°, around its y axis (i.e. the vertical axis passing through the middle of the cube) or around its x axis. This looks better, but we can only see two faces. Plus, the cube is now visibly rotated, which was not the intention.

Rotated cube52
Rotated cube. (View large version53)

Something else we could do is change our point of view. We do this via a property called perspective-origin. Its initial value is 50% 50%. This is relative to the scene, and we know that the 50% 50% point of the scene is where the central point of the shape is positioned. Now, let’s say we want to move this up and to the right. The simplest way to do this is to set perspective-origin: 100% 0. But this creates a problem: How we now see the cube depends on the dimensions of the scene (you can test this live54 by resizing the viewport).

Changing scene dimensions changes how we see the cube.

A perspective-origin of 100% 0 is measured from the top right corner of the scene, while the cube is always in the middle of the scene. Because of this, changing the scene’s dimensions will change the distance between the 50% 50% point (where the cube is positioned) and the 100% 0 point (where we have set the perspective-origin).

A solution for this to use calc() for perspective-origin, of course, is simply to add or subtract a fixed value from the initial 50%:

perspective-origin: calc(50% + 15em) calc(50% - 10em);


You can test this live55 by resizing the viewport.

What about you? Link

Have you used calc()? If yes, what for?

(ds, ml, jb, al)

Footnotes Link

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33 ''
  34. 34 ''
  35. 35 ''
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55

↑ Back to top Tweet itShare on Facebook

Somewhat of a scientist and chocolate reviewer. Often plays with code just for the heck of it. Mostly into algorithms, graphics, code golf.

  1. 1


    December 2, 2015 5:34 pm

    Spotted a mistake:
    “1dpi is always […] 96dppx”

    “1dppx is equivalent to 96dpi” according to

  2. 3

    Dillon de Voor

    December 2, 2015 6:16 pm

    Calc is awesome and I’m using it more and more. For example a sticky footer ( or having a box on a grid that spans more columns the same height of a box that spans one column when the height of that box depends on the width.

    Might want to emphasize even more that calc is all about mixing units, `height: calc(3/4*100vh);` can just as well be written as `height: 75vh;` for example.

  3. 4

    Great overview on calc()! One of the best tutorials I’ve ever seen and definitely the best one-page calc() tutorial. Thanks, Ana!

  4. 5

    Nice review !
    I think there is a typo in last piece of code of the section.
    `bg` should be `$bg` .

  5. 7

    In latter examples of pre-processors, SASS was used but wasn’t mentioned anywhere. Btw, I can understand it as I use SASS.

  6. 8

    Justin McDowell

    December 2, 2015 7:19 pm

    Wow, there are a lot of calc() tricks here! It’s one of my favorite properties to use these days because I feel like we’re just beginning to see it’s true power potential.

    My own personal favorite trick is min-margins, to always ensure your content will have at least a certain bit of space between it and the edge, and it takes advantage of mixing relative units you described above:

  7. 9

    I was pretty proud of myself when I figured out how to use calc() to center content of a certain width in its container without using a wrapper element…

  8. 10

    Zoltan Hawryluk

    December 2, 2015 10:20 pm

    The best article on calc() tricks I have read. Thanks for sharing.

  9. 11

    Be careful on what projects calc() is used on:

    We have started using calc() on projects were requirements allow us, but many projects don’t allow us the convenience.

  10. 12

    Yes, calc(), very useful but now I think I will try your guide on the CSS here in can a tutorial aimed at android phone views be done? i’ll think on it.

  11. 13

    This is really a good article. I have been using calc() for some time now, and it works good on newer browsers, but I have experienced issues on Android stock browsers on some devices, even with 4.4.2 browser which should have support for calc(). Is there some kind of workaround for this?

  12. 14

    Great article Ana! I use calc() all the time when I’m building grids of things. I like 15px gutters, so I’ll do something like:

    width: (50% – 7.5px);
    margin-left: 15px;

    And then cancel it on the :first-of-type. That way the items can resize, while maintaining that perfect 15px gutter :D

    It’s wonderful! You can see it in action on

  13. 15

    Mike Riethmuller

    December 8, 2015 1:31 pm

    Thanks Ana, this is great! I’ve been experimenting with `calc()` for a while myself and there is definitely some great stuff you can do with it. A few of your examples I haven’t seen or thought of using before reading this.

    Unfortunately some people still have a bit of a stigma around using `calc()` and I feel we might be missing out on some potentially interesting progressive techniques because of that. So it great to see articles like this with some practical examples people can use.


  14. 16

    The linear gradient. Why don’t the RGB values make sense? [CMY] = [1,1,1]-[RGB] and you choose obvious values, it makes perfect sense. The only thing that may be bad is all that interpolation though an RGB colourspace.

  15. 17

    Centering using

    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);

    …doesn’t require you to know the width of the element as the browser calculates it itself. Or if you know it, this method doesn’t care!

    Other than that – great article :)

  16. 19

    For centering I normally don’t use calc(), I use the transform attribute instead like that:

    .class {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 5em;
    height: 3rem;
    transform: translate(-50%, -50%);

    which centers the element always in the absolute middle, coz it moves the element itself 50% back in both directions x & y.

  17. 21

    Thank you very much for this publication.
    System of coordinates + grid #2 is a killer feature.

    Two weeks ago I have used calc() for fixed sidebar that should be fitted between fixed footer and header

    .height: calc(~"100vh -" (@navbar-height + @footer-height));


↑ Back to top