Using CSS Media Queries for Multi-Platform

By Shawn Borsky

The fun thing about CSS Media Queries is that there isn’t a whole lot of solid documentation on them. Most engineers are concerned with simply detecting mobile or not. It takes a fair amount of digging, trial and error and wall-punching to wrap your head around queries for specific uses such as Android.

At HotelTonight, we have a native iOS and Android app. Our team is looking to deliver a great user experience by serving up an app with platform specific conventions, familiar UI patterns and gorgeous optimized details. Why should our handling of mobile visitors to our website be any different?

Our products are mobile-only so it makes sense that we get fair amount of people checking us out on mobile. Crazy, right?

Alright back to queries.

As a quick crash-course, CSS media is capable of detecting device-width/height, width/height, pixel density, and orientation. Sounds simple. Don’t worry the fun is just beginning.

At HotelTonight, we are using CSS media queries to serve a platform specific, device proportioned mobile front door to visitors of our website. There are a couple of things that we do that may not work in every case. For instance, the mobile front door is portrait only so we force the device into portrait by default. Although, CSS media is capable of targeting orientation. We also disable zooming and scaling because we have no scrolling, inputs or blocked readable content. This allows us to have more predictable behavior from a device.

The Two Faces of Media Queries

In terms of mobile queries there are actually two tools implied with it. CSS Media and Viewport meta. The viewport meta in the head allows us to give certain instructions to the device browser before it renders and the CSS Media allows us to hand out specific style instructions based on the device attributes. I will be discussing a combination of methods and various cocktails of those two that allow you to achieve different results.

Before we jump into the actual magic queries, there are a number of interesting power struggles going on that are useful to know about.

Device Width vs. Width

The width question within queries is quite tricky. It becomes of central importance to understand because 90% of the time, dimensions are the only way you might have to tell two devices apart. The idea is that device-width returns the actual width of the device. For instance, a 1024 x 768 device should return a device-width of 768. This is actually refering to physical device pixels not the resolution. So, an iPad 3 actually has a screen display of 1536px x 2048px, but it will return a device-width of 768px.

Fun Fact: Most devices return device-width based on orientation. Except iOS devices. They will return the portrait value in both cases.

The width property refers to width of the browser window (the viewport). It seems like a browser on mobile should always be the same width as the screen. However, there are a number of problems with this assumption.The first being that screen density plays a signifcant role, as does the type of browser(Firefox,Safari,Chrome) and my favorite: target density scaling.

Target Density Scaling

The Android ecosystem has a fair amount variety in screen sizes and densities. As a result, the browsers on Android attempt to scale screens up or down to fit nicely on various screen sizes. By default, Android centralizes its efforts around MDPI density. This is typically refered to as ‘overview’ mode. This generally makes sure that width responses are not reliable at all. At the least they are not what you would expect.

To elaborate, in overview mode an Android device will scale the viewport’s width to 800px, which for most mobile optimized layouts ( which are typically 320px ) will result in the layout being far too small for the screen. With some of Android’s fluid approaches you can define the target density or the target width but this is not done in CSS and can only explicitly target static sizes. This is a problem for a layout that must work on iOS , Android and beyond. Due to the target-density scaling, this also means that the viewport width is being scaled proportional to the device resolution and target-density which results in some really goofy reported widths ( such as 603px width on some Android tablets).

You can disable target density scaling via the Viewport meta tag but that makes it harder to detect against multiple densities and therefore, makes our road to multi-platform detection harder.

If you are aiming for less specific queries then this viewport tag will do the trick:

<meta name="viewport" content="target-densitydpi=device-dpi">

!important

Since media queries render at a strange place within the stylesheet, they generally do not cascade very well. It usually a good idea to use the !important declaration to ensure that overlapping queries know exactly what to show the user. When a query renders, it is reacting to the device and viewport. Therefore, it is not aware of other queries so you have to ensure that style instructions are explicit. For instance, hiding an iOS device image in addition to showing the appropriate Android version.

Viewport First

The first thing that is required is to prime our viewport meta to stop the device browsers from ruining our day right off the bat. This is what we use for HotelTonight’s mobile front door.

<meta name="viewport" content="user-scalable=no, width=device-width, minimum-scale=1, maximum-scale=1">

Constructing the Query Set

In general, the most reliable differentiator for mobile devices is screen-pixel-ratio (pixel density).

With a query like this we can target a small sub-set of devices which includes iPhone4S, iPhone5, Nexus 4, Nexus 7, etc. Until recently this was a reliable way to target retina iPhones but as high density devices become more popular the query is getting less and less specific. Even so, it gives as fairly laser target for the amount of devices that could potentially see our site.

@media only screen and (-webkit-min-device-pixel-ratio : 2) {
  /*CSS*/
}

Next, we want to knock out some other general assumptions that help us narrow the field of devices that are detected. The easiest thing to do is to detect an average normal density smartphone which will give us a solid amount of coverage in handling minimal devices ( such as many of the Blackberrys and Palms). This also allows us to target the non-retina iPhone and many HTC devices.

@media only screen and (max-device-width: 480px) {
  /* CSS Instructions */
}

At this point, we are still hitting a large swath of Android smartphones with our query. Part of what we want to do is present the user with 1) a device specific screen and 2) a properly proportioned screen. This means further segementing our approach within Android devices.

We can use a query set like this to target the different device densities. You will notice that MDPI does not specify a property for pixel-ratio. As previously mentioned, the default Android device is trying to scale us into MDPI anwyay, so we just need to make sure to react to a standard MDPI device size. Usually goes without saying but in most cases, it is also good to modify the device-width properties with min and max to make sure our range is more responsive. However, when you are trying to laser target a device ( such as a Nexus 7) sometimes a specific width query is needed.

/* (ldpi) Android*/
@media only screen and (-webkit-device-pixel-ratio:.75){
  /* CSS  */
}

/* (mdpi) Android*/
@media only screen and (min-device-width : 480px) and (max-device-width : 800px) {
  /* CSS  */
}

/* (hdpi) Android*/
@media only screen and (-webkit-device-pixel-ratio:1.5){
  /* CSS  */
}

Handling Tablets

Done, right? Nope, the plot thickens with tablets (which are totally included in the mobile classifcation).

Tablets seem straight forward enough but they can also get confusing because they frequently have different resolutons vs. device sizes. Due to the resolution similarities to Android handsets its quite easy to overwrite handset queries with tablet ones.

We can start off easily and segment out iOS tablets. All current iPad versions (including Mini) report the same device properties so its actually fairly painless. ** Remember ** Unlike other devices, iOS doesn’t change its reporting based on orientation.

/* iPads (portrait and landscape) */
@media only screen and (min-device-width : 768px) and (max-device-width : 1024px) {
  /* CSS Instructions */
}

With Android tablets, its also required to look at the different browsers. In the case of the Nexus 7, Firefox and Chrome report their properties differently. Specifically, Chrome likes to report its viewport width differently than its device width, despite the viewport meta.

/* Android Nexus 7 - firefox */
@media only screen and (device-width : 800px){
  /* CSS Instructions */
}

/* Android Nexus 7  - chrome */
@media only screen and (max-width : 600px) and (device-width : 800px){
  /* CSS Instructions */
}

This query set works pretty well, but it has one problem. In terms of what the viewport reporting and what the CSS query will see: the iPhone 4S+ and Nexus 4 are the same device. Since they report the same dimensions and screen density. We handled it with a conditional in the mark-up. Basically, a method that checks the user agent. Further, this Android query uses a specific width property to force Nexus tablets out of the scope.

- if ios_device?
  :css
    /* iPhone 4+ */
    @media only screen and (-webkit-min-device-pixel-ratio : 2){
      /*CSS*/
    }

- else
  - if android_device?
    :css
      /*Android - Galaxy Nexus and Nexus 4*/
      @media only screen and (max-width : 603px) and (-webkit-device-pixel-ratio:2)  {
        /*CSS*/
      }

Why all the CSS Queries?

You might be wondering, why don’t we just exclusively use device agent or other methods of detecting devices? Mainly, we want to have control over how every element looks on every device. Without viewport detection and media queries it becomes a blind exercise to design something great looking for a mobile user.Not to mention, it would require a ton of potentially messy Javascript and other such tomfoolery to manipulate the DOM and switch images in and out.

The styles themselves have been striped from the above examples but a typical style for our CSS query looks like this:

/* (hdpi) Android */
@media only screen and (-webkit-device-pixel-ratio:1.5) {
  #ios_feature_phone, #ios_feature_phone5{
    display:none!important;
  }

  #android_feature_phone{
    display:none!important;
  }

  #android_feature_phone_hdpi{
    display:block!important;
    zoom:0.5;
    top:160px!important;
  }

  #ht_logo_mobile{
    zoom:3.5;
    top:40px!important;
  }

  #m_nav{
    top:462px!important;
  }
}

There are a lot of highly specific absolute values here. In most cases, this is not a great practice. However, we found that using a flowing layout with wildly different widths, heights and sizes was impractical and worth more headache then simply testing and handing the devices more targeted values. The relative zoom scale adjusts for small variations in devices and we found that density was generally a good guide for expected height within a mobile classification of device types.

We set out to deliver a great looking first web impression to a mobile user where we served them an appropriate call to action and platform. Hell it even works on Blackberry!

Written by Shawn Borsky

Read more posts by Shawn, and follow Shawn on Twitter.

Interested in building something great?

Join us in building the worlds most loved hotel app.
View our open engineering positions.