The tyranny of the media query

By Michael Argentini
Managing Partner, Technology and Design

If the march of progress has taught us anything, it's that when it comes to technology it's more of a sprint; in the dark.

Case in point: Apple at one time sold a single iPhone model, with no alternatives in sight. The claim was that it was the perfect form factor; pocketable, easily used with one hand. Obviously that point of view "evolved" to the point that we now have 8 different iPhone models being sold, as well as a few watch models. And Apple has recently announced that the next WatchOS update will render web pages. You may want to visit your optometrist. And this is just Apple.

My point is that it's hard enough to keep up with the current lineup of devices across manufacturers, who typically release new products on a 12 month cycle. A popular strategy for handling this problem is to try to qualify your target audience such that you focus on the devices they are likely to be using. But given the trend of "bring your own device" (BYOD) and the march of progress, that strategy goes right out the window. Do we really know what our audience is using? The best analytics data from your current platform will only show you what they're using today, and based on what they used in the past, you can try to divine what they'll be using in the future. But given that the industry evolves like a sprint in the dark, how realistic is it to make this prediction with any degree of accuracy?

Going back to Apple, remember how radically everything changed when the iPhone was introduced? It was a seismic shift in mobile computing. Even Google's newborn Android operating system had to be completely rethought and rewritten to keep it relevant given what Apple did to the industry at the time.

Responsive web apps are not scalable

When it comes to web layout we live in a world of mobile-first design, leveraging CSS media breakpoints with a more responsible strategy of progressive enhancement. The thought is essentially that if you can design a great mobile experience, with all the fluff removed to best use a tiny screen, you can incrementally add features and layout as the screen size gets bigger. The idea is sound, and is still the recommended practice in our industry.

But there are drawbacks. For example, in order to support the largest variety of device sizes and screen dimensions, you need to:

  1. use a large number of media query breakpoints to control the appearance of your web app at every meaningful set of screen dimensions and physical sizes, or...
  2. focus on a select few screen dimensions and sizes, to give 80% of the users the best experience, or...
  3. compromise on your layout so that it renders acceptably on as many devices as possible.

When you visit most websites today you'll see one or more of these strategies in practice. Here's an example from a national pet supply retailer.

On the left: You can see that on an iPhone SE the text column is very narrow and wrapping poorly. The call-to-action (CTA) button overlaps the carousel navigational nodes (the circles that show which frame you're viewing), and the carousel arrow buttons are also being overlapped by content.

On the right: On an iPhone 8 Plus the text is more readable, but the carousel arrows touch the screen edges. It's clear that the layout concession was to put each text item on a line by itself, and only support large phones because changing the carousel layout for smaller phones was either an oversight or deemed not to be worthwhile.

Many websites are even worse. But I thought this might be a good one to use given that national retailers put a premium on Internet sales and this syndrome even affects them. Keep in mind that this is only an example of two phone sizes. And this example is just one frame of a carousel at the top of the home page. You'd likely be reading for a month if we did a breakdown of the entire website. And that's just this one website.

What if the experience were truly scalable?

The solution to this problem lies in making your web applications truly scalable, like a PDF. That doesn't mean abandoning media queries. It means that using fewer breakpoints that target key screen dimensions and sizes will work for all the intermediate sizes. It means much less work, and more easily managed code. It also helps to future-proof your layout.

We take this approach with the Fynydd website using an open source project we created, named SFUMATO. Here is what the Fynydd website looks like on four different iPhone models:

The goal was to have the same experience across all handheld devices. To do this, we simply created a single media query breakpoint from the smallest size, and allow it to scale up for all other phone sizes. You can see that even the text is wrapping at the same points, giving the user a near pixel-perfect representation of the layout as if it were a print design (like a PDF).

This approach may not be the perfect solution for every web application, but it sure feels that way :)

How did we do it?

Essentially the solution isn't very complex, nor does it significantly deviate from the standard mobile-first development pattern. From a technical standpoint, the three key strategies you need to adopt are:

  1. Set a font size on the HTML element using viewport width units.
  2. Use rem units to size everything else.
  3. To achieve an elastic layout, where the scaling stops above a certain viewport width, you need to calculate what the viewport size would be in pixels at a given width and freeze the default text size accordingly. That will freeze all other rem-sized elements.

What does this mean? Essentially, if you set the default text size for your website using a scalable measurement so that it gets bigger and smaller as you resize your browser window, all other elements can be sized relatively based on that one measurement.

Our SFUMATO SCSS framework makes this much easier by providing a structure for your styles, and helpers that do the work of calculating the sizes to be used for an elastic layout.

Using standard CSS, here's an example of how you would set up a baseline for dynamic scaling of everything else in your website:

html {
    /* Default text size set to 2% of the browser window width */
    font-size: 2vw;
}

@media screen and (min-width: 1200px) {

    html {
        /* Default text size locked at a browser width of 1200 pixels
           or wider using the formula (max-width x font-size / 100)
           which becomes: (1200 * 2 / 100)px */
        font-size: 24px;
    }
}

h1 {
    font-size: 2rem;
}

This simple code snippet works wonders. Given this, all you have to do is size everything else (with a few exceptions) using rem units. These are "relative em units" which are a simply a multiplier for the base font size. So a value of 1rem is equal to 2vw (the default font size). And with a browser window at 1200 pixels or wider, 1rem would be locked at 24 pixels.

In the example above, h1 elements are twice the size (2 rem) of the normal text, and their sizes will be locked at 48px when the elastic browser width of 1200px is reached.

Scratching the surface

Once you get into the habit of thinking about sizes in terms of rems, you'll be well on your way. And once you have the basics down, you can start thinking about the exceptions.

One example of an exception is border widths. Generally speaking, they can be sized using rems without much of an issue. But because they can get really thin, fractional widths become unpredictable. An example of this would be a hamburger menu button. Those are the menu buttons with three lines that are commonly used to open navigation menus. There's one in the video example above. If the lines get too thin, the browser may render them at different thicknesses as an artifact of trying to maximize the rendering of multiple objects in a small viewing area. The way around that would be to give your lines a minimum width in pixel units.

Another example of an exception is column widths. Grid columns typically need to be sized at percentages. For example, if you have a two-column layout, you'd likely want each column to be half the width of the browser window. But if using an elastic layout, you'll also want the widths to freeze at a specific window width. One way to do this is to use viewport width units (vw) which are essentially a percentage of the window width, and then at an elastic width freeze the size using the same formula used above to bootstrap the text sizing.

.column {
    width: 50vw;
}

@media screen and (min-width: 1200px) {

    .column {
        /* At the elastic window width of 1200 pixels or wider
           use the formula (max-width x column-width / 100)
           which becomes: (1200 * 50 / 100)px */
        width: 600px;
    }
}

Again, percentages can work well without the breakpoint. But if the grid is for an outer-most container, you'll likely need to use the code above to ensure an elastic behavior. As I wrote previously, this is a basic example, but it shows the logic behind dynamic scaling, with an elastic freeze point.

The SFUMATO framework makes this MUCH simpler, given that its helper classes shorten the code and handle the math, it has a flexbox grid system that handles elastic layouts, it has a color theme system for easily setting and changing colors en masse, and more.

Would this help you too?

If you think that this could help you too, let us know! Just use the contact form below, or visit our contact page to reach out and tell us how we can help. We even have a public slack workspace you can join free, for longer conversations.

Article last updated on 7/11/2018