Convert pixels to rems automatically

Code 5 min read

Convert pixels to rems automatically

Last updated: July 30th, 2020

Using rems in your CSS is a great way to deal with the many hurdles you’ll face building out a design across various screen resolutions.

Before we get ahead of ourselves assuming everyone knows what we’re talking about, here’s a quick primer on rems if you’re uninitiated. Feel free to skip this part if you’re already in the know.

A rem stands for “root em” and our buddy Jonathan Snook tells us that “the rem unit is relative to the root-or the html-element. That means that we can define a single font size on the html element and define all rem units to be a percentage of that.”.

In most modern browsers, 1 rem is equal to 16 pixels. So with a base size of 1rem (a.k.a. 16px) set, we can now use simple division to figure out proper sizing of elements. A headline that is 24px in Sketch is coded as 1.5rem because you take 24px and divide it by the base font size of 16px. This relative unit conversion makes scaling fonts and layouts with media queries a breeze.

But this leads us to one of the biggest criticisms of using rem units in CSS: the insane amount of calculating it takes to get a design comp from pixels to rems.

We know that using relative units of measurement are a great way to handle responsive web design. But using a rem to pixel conversion tool over, and over, and over again gets a tad bit monotonous. There has to be a better way, right? Lucky for us, there is.

A quick word about workflow

Before we get started though, it’s important to note that this method only works if you have a pre-processor in place. We use SCSS and Codekit to handle our dirty work. If you need help setting these up, Codekit’s documentation is a great place to start. Now, onward!

Finding a way to convert pixels to rems automatically

In our search for efficiently managing rems, we inspected our own process. One things we did know: we wanted to have a 1:1 hand off from designers to developers. But what does that mean?

That means if a designer makes a headline 24px with bottom margin of 36px, we wanted that reflected in the code and easily identifiable.

We also knew that we didn’t want to waste the developer’s time dividing a ton of numbers by 16px to get a rem percentage for our responsive layout. That’s no fun.

So after turning over a lot of stones, we settled on a combination of ideas from Foundation and a mixin article from CSS Tricks. The end result is a function that will convert pixels to rems on the fly. Here’s the code, we’ll break down how it works in a minute:

$global-font-size: 100% !default;

/*
Removes the unit (e.g. px, em, rem) from a value, returning the number only.
@param {Number} $num - Number to strip unit from.
@returns {Number} The same number, sans unit.
*/

@function strip-unit($num) {
@return $num / ($num * 0 + 1);
}

/*
Converts one or more pixel values into matching rem values.
@param {Number|List} $values - One or more values to convert. Be sure to separate them with spaces and not commas. If you need to convert a comma-separated list, wrap the list in parentheses.

@param {Number} $base [null] - The base value to use when calculating the `rem`. If you're using Foundation out of the box, this is 16px. If this parameter is `null`, the function will reference the `$base-font-size` variable as the base.

@returns {List} A list of converted values.
*/

@function rem-calc($values, $base: null) {
$rem-values: ();
$count: length($values);

/* If no base is defined, defer to the global font size */
@if $base == null {
$base: $global-font-size;
}

/* If the base font size is a %, then multiply it by 16px
This is because 100% font size = 16px in most all browsers
*/
@if unit($base) == '%' {
$base: ($base / 100%) * 16px;
}

/* Using rem as base allows correct scaling */
@if unit($base) == 'rem' {
$base: strip-unit($base) * 16px;
}

@if $count == 1 {
@return -to-rem($values, $base);
}

@for $i from 1 through $count {
$rem-values: append($rem-values, -to-rem(nth($values, $i), $base));
}

@return $rem-values;
}

/*
Converts a pixel value to matching rem value. *Any* value passed, regardless of unit, is assumed to be a pixel value. By default, the base pixel value used to calculate the rem value is taken from the `$global-font-size` variable.

@access private
@param {Number} $value - Pixel value to convert.
@param {Number} $base [null] - Base for pixel conversion.

@returns {Number} A number in rems, calculated based on the given value and the base pixel value. rem values are passed through as is.
*/
@function -to-rem($value, $base: null) {
/* Check if the value is a number */
@if type-of($value) != 'number' {
@warn inspect($value) + ' was passed to rem-calc(), which is not a number.';
@return $value;
}

/* Transform em into rem if someone hands over 'em's */
@if unit($value) == 'em' {
$value: strip-unit($value) * 1rem;
}

/* Calculate rem if units for $value is not rem or em */
@if unit($value) != 'rem' {
$value: strip-unit($value) / strip-unit($base) * 1rem;
}

/* Turn 0rem into 0 */
@if $value == 0rem {
$value: 0;
}

@return $value;
}

Taking a look at the conversion process

Let’s say you have a file called styles.scss. To implement, you place this function at the top of the file so you can call on it throughout the cascade. Once you have your file in order, you simply pass the function rem-calc() where you want to output pixel-to-rem values. The arguments can either be a single number or multiple numbers, separated by a space (see examples below).

Let’s take a simple example of adding bottom margin to a container div with a class of .container.

To add 10px of bottom margin, you would add your CSS like this using the function value from above:

div.container {
margin-bottom: rem-calc(10); /* 10 is the amount of margin you want in pixels */
}

Once run through the processor, you’ll see the output as:

div.container {
margin-bottom: 0.625rem; /* 10 divided by 16 */
}

To use this function with multiple parameters, setting up multiple margins in your CSS would look like this:

div.container {
margin: rem-calc(11 16 12 8); /* Numbers are the amount of margin you want in pixels */
}

With the output after processing being:

div.container {
margin: 0.6875rem 1rem 0.75rem 0.5rem; /* All margins divided by 16 */
}

A practical way to make your site more flexible

Most design tools output their values (distances, font sizes, dimensions, border widths, etc.) in pixels. With this function, you won’t have to worry about making those pesky conversions anymore.

Designers are happy because their designs stay true to scale. Developers are happy because they don’t have to whip out their calculators every 5 minutes.

The end result are more flexible web sites and applications and a delighted team. It doesn’t get much better than that!

8 Comments

Francisco October 23, 2018

I tried out this function and it throws this error:
“Stack depth exceeded max of 1024”
They size of my scss file is not that big, so I guess this function only works for really small files and specific cases.

Tiago Duarte October 24, 2018

Francisco, unfortunately that error message is a bit vague so it’s hard to say for sure what could be going wrong, but after googling around for a bit it doesn’t look like it’s the result of the function above. Here’s what I found:

1. It’s possibly a OS compatibility issue https://github.com/sass/libsass/issues/2402

2. Or perhaps an outdated version of SASS itself, or of a tool you might be using to process it (could be all the way up from Node down to Gulp/Grunt/Gulp-sass/etc): https://stackoverflow.com/questions/41474319/node-gulp-for-sass-stack-level-too-deep

Hopefully that’s helpful! I’m curious to know if/how you manage to get it working!

davide January 25, 2019

HI! I have a couple of doubts
1. why did you include this “/* Transform em into rem if someone hands over ’em’s */”. i mean what if i also wanna use rem?
2. I heard setting the font base of the html in % is the best practice, now regarding this: “/* If the base font size is a %, then multiply it by 16px This is because 100% font size = 16px in most all browsers*/” i wonder, what’s the point now of setting the font base in %, cause it will be traslated in 16px anyway, no?

thanks very much,
Davide From Italy

Tiago Duarte January 25, 2019

Hi Davide, these are great questions!

Regarding 1., perhaps you meant using ’em’ instead? If you want to use ‘rem’ directly then you can, but this method isn’t really helpful in that case.
If you meant ’em’ instead, you can pass `em` values into the function but they will be converted to pixels, since this approach encourages you to stick to just pixels and
‘rem’ values. If you’d still prefer to use `em` values though, there are other SCSS functions that can help you with that too: https://css-tricks.com/snippets/sass/px-to-em-functions/

As for 2., I’d argue it’s probably due to two reasons: 1) a good old habit from the days when IE7 was still commonly used. IE7 doesn’t resize fonts when zooming in/out if they’re set in anything other than a percent value. 2) Using a percentage value as the font size in the element ensures that fonts will increase/decrease in size properly when zooming in/out, or when changing your browser’s default font size – ’em’ values on the other hand can behave unexpectedly. Here’s a bit more info on this: https://kyleschaeffer.com/css-font-size-em-vs-px-vs-pt-vs-percent

Hopefully this was helpful!
Tiago

Clint November 12, 2019

Hey there, nice write up but it seems all your code samples are gone.

Matt Downey November 12, 2019

Hey Clint, sorry about that—I think there was a plugin conflict.

The examples are back for me, are you able to see them now?

Wesley Moody July 30, 2020

As Clint said in November, your code samples are missing. I thought it was just me and I looked about 4 times up and down the article but there’s nothing.

Matt Downey July 30, 2020

It’s not very pretty, but I killed the plugin that handles the highlighting syntax and just added tags by hand. At least you can read it now, I'll work to getting it looking more formatted shortly. Thanks, Wesley!

Join the conversation, leave a comment below

This site uses Akismet to reduce spam. Learn how your comment data is processed.