REM based media queries are weird

Relative units (such as rems) in media queries might not behave as you expect. Let’s look at how, when and why they are weird.

8 minute read

Relative units (such as rems) in media queries might not behave as you expect. The TL;DR is that 1rem in a media query condition is always equal to the font-size set in browser settings, and is not affected by a root font-size assigned in CSS. If this is not news to you, or you hold an unwavering opinion that you should never set a root font size, there’s probably nothing more to see here. If everything you thought you understood about rems is crumbling under your feet or you’re open minded about setting a root font size, join me as I walk through the considerations.

What’s a REM based media query #

By this I mean a media query that uses a rem value in the media condition, something like this:

@media (min-width: 50rem) {
/* my CSS here */
}

The issue I’m discussing here is true of all relative units though, not just rems, so also applies to:

@media (min-width: 50em) {
/* my CSS here */
}

What do you mean they are weird? #

By weird I mean that 1rem in a media query condition, is not necessarily equal to 1rem elsewhere in your CSS. While 1rem everywhere else is equal to the font size at the html element, in media queries it is always equal to the font size set in the browser preferences.

Relative length units in media queries are based on the initial value, which means that units are never based on results of declarations. For example, in HTML, the em unit is relative to the initial value of font-size, defined by the user agent or the user’s preferences, not any styling on the page.

Media Queries Level 4

So if you change the size of a rem in your CSS, it diverges from the size of a rem as far as media queries are concerned. I don’t think that is what anyone would expect. I believe the reason for this implementation is to avoid infinite loops. In case for some reason you tried to do this, for example:

html {
font-size: 10px;
}
 
@media (min-width: 50rem) {
/* at browser width of 500px do this */
/* browser width is 600px, let's do this */
html {
font-size: 20px;
/* oh wait, rem value has changed, breakpoint is now 1000px */
/* leave the font-size at 10px */
/* oh in that case the breakpoint is 500px, let's do this */
/* ok font-size changed to 20px, breakpoint is now 1000px */
/* leave the font-size at 10px... */
}
}

Why not just use pixels in media queries? #

You absolutely can, as with all these questions you just need to be clear what that means for your project. The difference between using px or rem in a media query comes when the font size is changed at the browser setting level. If that font size is made significantly larger, and you are using media queries to determine how much space is afforded to the text, this question becomes significant. Say for example you have a column of text, and on large screens a sidebar sits next to that text (dependant on media query). You added the sidebar at the breakpoint you did because you’ve deemed there to be enough space for both. If the font size is larger though, is there still enough space for both? A pixel media query doesn’t care about the size of the text and adds the sidebar regardless. With a rem media query on the other hand, that breakpoint will grow with the font size, meaning the text proportionally retains the amount of space you originally deemed appropriate.

So just avoid setting a root font-size? #

In practice rem weirdness is one of many things to consider when deciding where and how to use pixels and/or rems. I don’t think there is a right or wrong approach, or really any rules to follow. I’d much rather be aware of the consequences of the choices, and make those choices based on the project I’m working on. Not setting a root font size will avoid the media query weirdness, and is perhaps a good default. Let’s look at some considerations you need to make when considering setting your own root font size. First of all, you probably don't want a fixed value font size (such as px) on your HTML element. Doing so prevents your visitors from using the font size they have set in their browser. This is as close as I'm going to come to a rule here (I would argue there are reasonable exceptions). You almost certainly don't want to be doing this:

html { font-size: 16px; }

In that case your best solution is to leave the HTML element alone. Having no font size set both avoids an accessibility blunder and keeps the rem media query story simple.

As you start to make a stronger case for setting a root font-size, the decision of whether to do so gets trickier. Some people like to do this for example:

html { font-size: 62.5%; }

In this case the change has been made in order to make the equation for converting pixels to rems easier (1rem now equals 10px). If used responsibly, this is probably not a terrible idea; percentages preserve the ability to influence the font size from the browser setting, and presumably you are going to bump the sizes back up to something resembling that setting. You need now though to weigh up what you are gaining - easier mental maths, vs what you are losing - among other things the ability to use rems in media queries without them being weird. In this case I’d suggest finding a different solution to the maths problem, get a preprocessor to do it for example, or maybe even a native CSS calc():

p {
font-size: calc(18rem / 16); /* a rem value equivalent to 18px */
}

So far my advice has been to leave the HTML element alone. For some that seems to be a rule set in stone, but you can do some neat things with root font sizes. While html { font-size: 62.5%; } is fine but avoidable, something like this is really powerful and uniquely reliant on setting a root font size:

html {
font-size: calc(1em + 0.5vw);
}

This kind of thinking opens up possibilities for an elegant approach to responsive design that reduces reliance on breakpoints. I do this sort of thing quite a lot (I did it on this blog), and I think it's a legit argument against leaving the root alone. This is the point where I stop saying “leave the html element alone” and start saying “your approach depends on the requirements of your project”. Just know that if you do this... rems in media queries get weird. Whether this is a problem will depend on your project, the way you work, and your willingness to simultaneously hold two ideas of what a rem is. Having set a root font size you still get the same benefits when using rems in media queries as you would otherwise. Where you might get caught out is if you need a media query that relates to something which has a size influenced by rems.

An example #

How might weird rems cause a problem? Essentially if you try to use a media query to accommodate something on the page which is sized in rems. If you allow your mind to drift into a magical world where rems in media queries are not weird, you might think this is easy. You might do something like this:

.thing {
width: 100%;
}
 
@media (min-width: 50rem) {
.thing {
width: 50rem;
}
}

This is a silly but hopefully illustrative example where we say if the browser is wide enough to fit a 50rem thing in it, make the thing 50rem wide. But of course, if you have decided to diverge your rems (set a font size on your html element) then those two occurrences of 50rem might represent wildly different pixel values - the first being 50 x the font size in the browser settings, the second be 50 x your html font size. Kind of weird, huh?

Will it always be this way? #

In my opinion it would be great if 1rem was consistent wherever it was used. Not so much to avoid confusion, but to be able to reliably and usefully use rems with media queries. Essentially to be able to query “does a 50rem wide thing fit on the screen?” I guess at this point we’d need a new syntax for that, and I have no idea how difficult this would be or if anyone else even cares. The behaviour I’ve discussed here is not at all new, but I feel like the issue of infinite loops is something we are going to run into more as we transition from a heavy reliance on media queries, to sizing things more intrinsically. Infinite loops were for a long time cited as the reason we can’t have container queries. Maybe the solutions being worked on there could be applied to media queries too; or maybe container queries just remove the need for a fix. Hopefully container queries don’t end up being weird.

Thanks to torchlight.dev for the syntax highlighting in this post.

Start a conversation on Twitter. Feel free to @ me.