There’s more to the CSS rem unit than font sizing

Avatar of Roman Rudenko
Roman Rudenko on (Updated on )

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

The following is a guest post by Roman Rudenko, a mad scientist at Mobify where he’s tasked with understanding why browsers misbehave and with cajoling them into playing nice. When he’s not coding, Roman is learning to ride a motorbike without crashing into too many solid objects.

Many web designers and developers are familiar with the CSS rem length unit. But, you may not know that it has a couple of handy alternate uses. In this post, I’ll describe how to use the CSS rem unit to scale specific page elements while leaving others unaffected. I’ll also discuss how to use it as a replacement for the under-supported vw (viewport width) unit.

For readers unfamiliar with the rem unit (standing for “root em”), it provides a way to specify lengths as fractions of the root element’s font size. In practical terms, this almost always means the font size of the <html> element. The most obvious use for rem is replacing em to protect inner element font sizes from being changed by outer elements, thus avoiding the classic nested em scaling problem. But rem effectively operates as a custom adjustable unit, so it can be useful for other things too.

First, let’s look at how rem works and how it differs from the em unit. An em value is calculated against the font-size of a current element, so boxes sized with it will consequently scale as font sizes are adjusted by the element or its ancestors. Meanwhile, rem is defined as the font-size of the root element. So, all references to rem will return the same value, regardless of ancestor font size. In effect, rem is a document-wide CSS variable. Browser support for rem is quite good these days. It’s limited on desktop because IE8 does not support it, but common mobile and tablet browsers handle it much better.

Rem can be used for its typical font sizing duty. When markup nests in complex ways, font-size declarations with em values may compound unexpectedly, and rem is a good choice for untangling those issues. However, there are some other interesting uses of rem.

Scaling document elements

You can use rem to scale some elements in a document while leaving others in place. In this example, the font-size of secondary page content (slideshow controls, illustration names, post metadata) is controlled via rem, but the primary content remains sized in pixels.

Check out this Pen!

When one of the red size adjustment links is clicked, a small piece of JavaScript adjusts <html> font-size, resizing secondary elements without altering primary ones.

This style of sizing can be useful for user-driven customization, or to adapt layouts for cases that require secondary elements to be more touchable (tablet) or visible (TV). Without rem, every adjustable element would have to be resized separately.

/* States */
html.secondary-12px { font-size: 12px; }
html.secondary-14px { font-size: 14px; }
html.secondary-16px { font-size: 16px; }

/* Primary content stays fixed */
body { font-size: 18px; }

/* Secondary content scales */
.post-inner .meta { font-size: 1rem; }
.figure .title { font-size: 1rem; }
.slideshow .controls { font-size: 1.25rem; }
.slideshow .controls a { padding: 0 0.5rem; }

Replacement for vw

The CSS vw unit is defined as 1/100th of viewport width. This unit is useful for avoiding compounding in width calculations, just as rem avoids font-size multiplier compounding. It can also be used for non-width properties, such as font-size (fitting a fixed fragment of text into a percentage-sized box) or height (preserving element aspect ratio). Support for vw is still spotty. So, we can use rem instead and dynamically recalculate <html> font size to match vw unit size.

Let’s have a look at an example implementation for this workaround (resize browser to see adjustments):

Check out this Pen!
body { font-size: 12px; margin: 0; padding: 0;}
.one, .two {
  border: solid #666;
  border-width: 10px; border-width: 0.01rem;   /* 1vw */
  border-radius: 30px; border-radius: 0.03rem; /* 3vw */
  font-size: 20px; font-size: 0.02rem;         /* 2vw */
  padding: 20px; padding: 0.02rem;             /* 2vw */
}

Here, the <body> element font-size rule insulates content from changes in <html> font-size. Browsers that don’t understand rem are given a pixel-based fallback; browsers that do know rem derive the proper dimension from <html> font size.

An earlier version of this example allowed browsers that recognize the vw property to use it directly, but that was pulled after Chrome 25 failed to apply a border width specified with vw. Rem-based ersatz replacement had no such issue.

It was possible to do all of this before the rem unit was available by computing and assigning properties directly with JavaScript. But, rem makes things much easier. With rem, there’s only one assignment to make for the entire document, and the script doesn’t need to remember which properties should be scaled in what exact manner.

(function (doc, win) {
    var docEl = doc.documentElement,
        recalc = function () {
            var clientWidth = docEl.clientWidth;
            if (!clientWidth) return;

            docEl.style.fontSize = clientWidth + 'px';
            docEl.style.display = "none";
            docEl.clientWidth; // Force relayout - important to new Android devices
            docEl.style.display = "";
        };

    // Abort if browser does not support addEventListener
    if (!doc.addEventListener) return;

    // Test rem support
    var div = doc.createElement('div');
    div.setAttribute('style', 'font-size: 1rem');

    // Abort if browser does not recognize rem
    if (div.style.fontSize != "1rem") return;

    win.addEventListener('resize', recalc, false);
    doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);

There are a few things to note in the above JavaScript. First, the script will terminate early if the browser is out-of-date and doesn’t support the addEventListener API or the rem unit itself. Second, instead of setting <html> font size to an actual vw unit, it’s set to the viewport width in pixels. This means the CSS would need to scale vw values by a factor of 100 for use with rem, but avoids rounding bugs that would otherwise arise due to the limited font-size precision in Webkit.

Other viewport units can be supported in pretty much the same way. There are downsides to this approach. For instance, you can’t use rem for other purposes. And, you can only make use of one viewport unit (vw) at a time and so can’t use others like vh, vmin, or vmax.

While almost no production browser today supports true CSS variables, quite a few let you cheat a bit and get one in the form of the rem unit. If you don’t need to use rem for normal font sizing, you can use it for runtime scaling of pretty much anything. If you must use rem for font sizing, you have the option of using CSS preprocessors (Sass or Less) to compute proper font dimensions, leaving the rem unit free for runtime trickery.