Stop using em in media queries

Published

CSS media queries are used to create responsive layouts. Conventional wisdom tells us that we should use relative font sizes to specify layout dimensions, because that improves accessibility, however this wisdom is outdated in the current browser landscape.

Here's an example of a media query in vanilla CSS:

/* Hide the navigation sidebar on small screens. */
#navigation-sidebar {
  display: none;
}

/* Show it on large devices. */
@media (min-width: 1024px) {
  #navigation-siderbar {
    display: block;
  }
}

You are already using media queries to design responsive layouts, either like that example, or using breakpoints with Tailwind CSS or other CSS framework, or maybe even a useMediaQuery React hook in JS. At the end of the day, responsive designs come down to setting up breakpoints using media queries.

Media queries can be specified in px, of course, but also with rem and em units. Conventional wisdom tells us that we should always use em and rem units, so that our design adapts to the user's preferred font size. Today we're going to reevaluate this conventional wisdom.

The spec says that in a media query, "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." (Source). All browsers these days use 16px as the default initial font-size, although users can customize this in browser settings and with user-agent stylesheets.

Up until the mid-2010s, users could also change the font size using the browser's "zoom in" feature. However, this generally wreaked havoc on the site layout: designers wanted the sidebar to be able to fit a certain number of characters, but changing the font size without making the sidebar bigger breaks the design. This led to two things happening. The first is that developers learned that in order to create proper responsive designs, it's necessary to use em and (later) rem units.

The second thing was that browsers stopped doing the thing that broke the site layouts. These days, the browser's "zoom in" feature actually zooms in the pixels of the page, instead of just the font size. This makes things much easier on website designers, because a zoomed in page is just the same as a smaller browser window; and it's generally what a user expects to happen when they "zoom in". Notice how the entire box (not just the text) gets bigger as the page zoom changes:

How does this apply to media queries? In the old days, we would definitely want to use em units, because that would come closer to emulating the full-page zoom that we wanted, but browsers didn't give us. However, since browsers now support full page zooming, this benefit doesn't matter. But it gets worse: not all browsers agree on the interpretation of the spec!

Recall that media queries are specified to use ems "relative to the initial value of font-size, defined by the user agent or the user’s preferences". But what does that mean? Chrome and Firefox define this as 16px (again, customizable), but Safari works differently: it's 16px times the page zoom level. This creates an awkward situation where your media queries are zoomed more than your text is! For example, let's look at this media query (try on CodePen):

<style>
  body { background: #fb7185; }
  /* 40em = 640px / 16px */
  @media (min-width: 40em) {
    body { background: #22d3ee; }
  }
</style>
<script>
  const content = document.getElementById("content");
  function update() {
    const px = window.innerWidth;
    content.innerHTML = `<p>The window is ${px}px / ${(px / 16).toFixed(2)}em wide</p>`;
  }
  update();
  window.addEventListener("resize", update);
</script>

Now let's see what happens when we zoom in and out in Safari:

Look at that, everything looks normal as we zoom in, keeping our page above the 40em width even after we zoom in twice. But if we refresh, we see that Safari is actually calculating the media query based on the initial font size, which comes out to 800px (40em * 16px * 125%), and when we zoom back out, the media query is not reevaluated, and so the media query only applies after our browser window is zoomed out to 800px wide.

Is this a bug? Probably. Safari says that a media query of 1em is the same as 16px * page zoom level, say, 20px at 125%. But if you made a div and specified a width of 20px, it would too wide to match the media query, since all pixels are actually 1.25 pixels large at that zoom level. I think it's also definitely a bug that Safari isn't recalculating these values as the page is zoomed, since no reasonable user would want the page layout to shift after refreshing the page.

Recommendations

So the situation in 2023 is that all major browsers support full page zooming, which allows users to zoom in pages while preserving the developer's layout intent. We want to respect the user's font preferences in general, but full-page using pixel values in media queries leads to better results, due to a bug in the second-most-common web browser, with 20% market share. So, you should always write media queries in pixel values, using 16px = 1em as a guide.

Always write media queries in pixel values, using 16px = 1em as a guide.

This is already the consensus amongst the largest players on the web. A quick survey of how media queries are used in various frameworks and popular sites turned up this information (as of May 2023):

  • Frameworks: Tailwind CSS, Bootstrap, and MUI all use pixel values in their responsive design queries.
  • Popular sites: Google Search, Youtube, and Facebook all use pixel values in their media queries.
  • Sites by "CSS experts": MDN, CSS-Tricks, and CodePen all use pixel values in their media queries.

Overall, the accessibility situation on the web is better than ever, but unfortunately we still live in a world with browser bugs. If you want your site to render the way you intended to users with low vision, you should keep this in mind and use em and rem units everywhere--except your media queries. Keep those in px.

A picture of Ryan Patterson
Hi, I’m Ryan. I’ve been a full-stack developer for over 15 years and work on every part of the modern tech stack. Whenever I encounter something interesting in my work, I write about it here. Thanks for reading this post, I hope you enjoyed it!
© 2020 Ryan Patterson. Subscribe via RSS.