Media query rem and em values relative to `html`

As a web developer, I’d like to have the choice to have my media query rem and em values relative to the font-size of the html element or relative to the browser’s root font-size.

At the moment, we only have one option: relative to the browser’s root font-size.

The problem

If we leave the html element’s root font-size alone (just go with the browser’s default font size which is usually 16px), then all is fine and dandy.

However, in practice, it can be very useful to scale the font size up or down depending on device size (for example). This technique adapts font size to the device, for example, so that on a huge display (f.e. a 4k widescreen monitor) the fonts can be scaled up and make use of the all the new space.

However, when using this technique, it is impossible to scale the media queries to match the new proportions. This means that on very small or very large displays, scaling of the font size does not adjust the layout as expected.

Currently, to achieve the desired result, one has to leave the default browser root font size alone, then scale the browser’s font size (in the browser settings). This will also scale the media queries. It leads to a result where the layout matches with the font size.

But in developer land (which has no control over browser user font size), the layout can not scale with the font size.

This is too bad. :sob:.

Here’s an example of media queries with scaling font sizes based on the browser’s root font size (written with Sass):

// based on 16px default font size:
$XXS: 10em; // 160px
$XS:  20em; // 320px
$S:  34em; // 544px
$M:  48em; // 768px
$L:  67.5em; // 1080px
$XL: 100em; // 1600px
$XXL: 125em; // 2000px

// configuration for include-media Sass library
$breakpoints: (
  'XXS': $XXS,
  'XS': $XS,
  'S': $S,
  'M': $M,
  'L': $L,
  'XL': $XL,
  'XXL': $XXL,
);

/* scale the fonts based on device size */
html {
    font-family: SF, sans-serif;

    @include media('>=XXS') {
        font-size: 50%;
    }

    @include media('>=XS') {
        font-size: 60%;
    }

    @include media('>=S') {
        font-size: 70%;
    }

    @include media('>=M') {
        font-size: 80%;
    }

    @include media('>=L') {
        font-size: 100%;
    }

    @include media('>=XL') {
        font-size: 120%;
    }

    @include media('>=XXL') {
        font-size: 140%;
    }
}

As you can see, the media queries are based on the browser root font size, and they won’t scale with the html element’s font size. This will have the same effect as specifying layouts using px units and scaling the browser font size: what happens is the font gets bigger, but the layout does not adapt, leading to ugliness.

The solution

It’d be great if media queries were based on html element’s font-size.

I’m not yet sure what’s the best solution for the circular dependency (infinite loop) problem, but there has to be a way to make it work. The loop problem is that, the media query is based on html font size, which changes the html font size, which changes the media query, which changes the html font size, which changes the media query, etc…

Maybe there can be some sort of rule: f.e. the loop is executed at most once, or similar:

  1. Calculate initial font size of the html element based on browser root font-size
  2. Use value from 1 and run the media queries.
  3. If the media queries change the font size of html, calc the new font-size
  4. run media queries one more time and the set of matched styles. Ignore a new html font-size if it changed this time.
  5. when window resizes, do this over again, but start step 1 with the last determined font-size instead of the browser root font size.

Basiclly, let media queries change the html font size at most once. I’m sure this isn’t perfect, but maybe some sort of rules like this can give developers more flexibility.

The benefit

Responsive font sizes with responsive layout, and also responsive to user’s font setting.

Alternative, but with the same problem (that layout breakpoints don’t scale)

There’s a way to achieve this without scaling the base font: using transform: scale and width.

div {
  width: 100%;
  height: 100%;
  height: auto;
  background: deeppink;
  border: 1.25rem solid cyan;
  box-sizing: border-box;
  padding: 1.25rem;
}

html, body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

:root {
  --scale-xs: 0.8;
  --scale-s: 0.9;
  --scale-m: 1;
  --scale-l: 1.2;
  --scale-xl: 1.4;
}

html {
  transform-origin: top left;
  overflow: hidden;
  overflow-y: auto;
}

/* mobile-first default */
html {
  transform: scale( var(--scale-xs) );
  width: calc( 100% * 1 / var(--scale-xs) );
  /*height: calc( 100% * 1 / var(--scale-xs) );*/
}

@media ( min-width: 40rem ) {
  html {
    transform: scale( var(--scale-s) );
    width: calc( 100% * 1 / var(--scale-s) );
    /*height: calc( 100% * 1 / var(--scale-s) );*/
  }
}

@media ( min-width: 50rem ) {
  html {
    transform: scale( var(--scale-m) );
    width: calc( 100% * 1 / var(--scale-m) );
    /*height: calc( 100% * 1 / var(--scale-m) );*/
  }
}

@media ( min-width: 65rem ) {
  html {
    transform: scale( var(--scale-l ) );
    width: calc( 100% * 1 / var(--scale-l) );
    /*height: calc( 100% * 1 / var(--scale-l) );*/
  }
}

@media ( min-width: 80rem ) {
  html {
    transform: scale( var(--scale-xl ) );
    width: calc( 100% * 1 / var(--scale-xl) );
    /*height: calc( 100% * 1 / var(--scale-xl) );/*/
  }
}

Live demo: https://jsfiddle.net/mfvjae9y/5/

This is interesting because we can use rem values in the layout, We could take this further, and use a CSS-in-JS implementation to detect the width of the HTML element, and apply certain layouts based on the values we see in the JS (which is the feature we’re missing, that I’m saying would be nice to have).

In the end, it would be possible to achieve the loop breaking idea in JS. Maybe I’ll try to make a demo when I get more time…

I think if we had container queries then media queries would largely disappear. If we could query the min-/max-width of parent elements, then using their calculated values of rem would make sense.