HTML & CSS
Article

Cutting the Mustard with CSS Media Queries

By Andy Kirk

Cutting the Mustard is a term coined by Tom Maslen at the BBC. The method uses JavaScript that checks for browser capabilities before loading further CSS and JavaScript to give the user an ‘enhanced’ experience, otherwise the browser will only load the files necessary for a ‘core’ experience.

There has been a flurry of interest in cutting the mustard of late, for example Migrating to Flexbox by Cutting the Mustard and Server Side Mustard Cut, and in Progressive Enhancement in general.

Doing Better

In my experience, however, even a very basic ‘core’ experience can cause problems on some very old browsers, so I wanted to build on this idea and see if it was possible to deny really old browsers any CSS at all, leaving them with only the HTML.

Of course, the obvious solution is to use JavaScript to conditionally load all the CSS if the browser cuts the mustard, but this felt too heavy-handed for my liking; modern, capable browsers that didn’t load the JavaScript would be penalised by having no styles at all. So I looked for a CSS-only approach to the problem and I found an old post by Craig Buckler. After a fair bit of experimentation and adaptation, I came up with this:

<!-- in the <head> -->
<link rel="stylesheet" href="css/your-stylesheet.css"
      media="only screen and (min-resolution: 0.1dpcm)">
<link rel="stylesheet" href="css/your-stylesheet.css" 
      media="only screen and (-webkit-min-device-pixel-ratio:0) 
      and (min-color-index:0)">

Let’s examine what’s happening here.

How it Works

The first <link> element’s media query allows the stylesheet to load only in the following browsers:

  • IE 9+
  • FF 8+
  • Opera 12
  • Chrome 29+
  • Android 4.4+

The second <link> element’s media query allows the stylesheet to load only in:

  • Chrome 29+
  • Opera 16+
  • Safari 6.1+
  • iOS 7+
  • Android 4.4+

When combined, this technique will not load the stylesheet unless the browser is:

  • IE 9+
  • FF 8+
  • Opera 12, 16+
  • Chrome 29+
  • Safari 6.1+
  • iOS 7+
  • Android 4.4+

Note: the stylesheet is only loaded once no matter how many <link> elements there are.

It’s possible to combine the media queries into one link element by separating the declarations with a comma, like so:

<link rel="stylesheet" href="css/your-stylesheet.css"
      media="only screen and (min-resolution: 0.1dpcm),
      only screen and (-webkit-min-device-pixel-ratio:0)
      and (min-color-index:0)">

However, I personally like to keep them separate as I find it easier to see what’s going on.

So if you structure your markup in a semantic and accessible way, older browsers should still be able to see your un-styled content in plain HTML.

This is of course very opinionated, but it’s my view that anyone using these browsers still deserves to be able to get to what they need. It’s quite likely that by giving these browsers CSS intended for newer browsers, some things will just break, and that could mean a totally unusable page. Using this method they at least get a clean slate. More importantly, you’ll never need to test in those browsers again. You’re done with them! At least, that’s the theory.

Of course, your support needs are likely to vary, but the great thing about this technique is that you can build on it. For example, if you need to add support for IE8, you can use a conditional comment to load the same stylesheet only for that browser:

<!--[if IE 8]>
<link rel="stylesheet" href="css/your-stylesheet.css">
<![endif]-->

Or, if you need to support older WebKit devices with a pixel ratio of greater than 1, you could add another <link> element with a targeted media query, like this:

<link rel="stylesheet" href="css/your-stylesheet.css"
      media="only screen and (-webkit-min-device-pixel-ratio:1.1)">

By itself, this will load the stylesheet only for Android 2.2+ (I wasn’t able to test earlier versions), but since it’s included in addition to the other <link> elements, it’s effectively just adding support for this other group of browsers.

It’s also possible that you can alter the main <link> element’s media queries from how I’ve presented them here to get the custom support you need. However, it took quite a lot of testing to get this right, so be warned!

“But, they’re hacks!”

Yes, I suppose they are, though that depends on your definition. Media Queries are designed to test the capabilities of the browser before applying the CSS, and that’s exactly what we want to do in this case, albeit indirectly.

Still, hack or not, I’m pleased with this technique and it’s been working well for me in all the testing I’ve done so far, and I plan to use it on a production site soon.

Where it Doesn’t Work

In all my testing to date, I’ve found only one case where things don’t work as expected. On Android 4.4 the UC Browser doesn’t respond to the media query. From what I can tell, the UC Browser uses an older version of WebKit, which would explain things.

If you want to support this browser, it’s possible to resort to a little user agent sniffing to force the CSS to load:

if (navigator.userAgent.indexOf('UCBrowser') > -1) {
  var link  = document.createElement('link');
  link.rel  = 'stylesheet';
  link.href = 'css/your-stylesheet.css';
  document.getElementsByTagName('head')[0].appendChild(link);
}

As a bonus, as far as I can tell there’s no way to disable JavaScript in the Android UC Browser, so the stylesheet should always be loaded, barring network failures and such.

Of course, if you find there are other specific browsers you need to support, you can always add to the user agent sniff condition but I caution you to use it sparingly as it defeats the purpose of this being a CSS-only technique.

I’ve made a test page to test the base method on different browsers. If you find any browsers where things don’t work as expected, or if you find any other Media Queries that can add support for a particular browser or range of browsers, please let me know in the comments or raise an issue on the GitHub repository.

What About Loading Scripts?

Ok, so if you’re using CSS to detect browser support, there’s a good chance you’ll want to load or run some or all of your scripts only in browsers that you’re supporting. So how do we do that?

Well, there are several ways this can be achieved, but the simplest one I’ve found works like this:

  • In your stylesheet, insert a harmless, non-default CSS property on the body element.
  • Use JavaScript and getComputedStyle on the body element to determine if your property is set.
  • If it is, run or load your other JavaScript.

For example, the clear property has a default value of none. Setting it to both on the body is unlikely to have any impact on the display of your page (if it does then you’ll have to use a different property). So the code would look like this in your stylesheet:

body {
  clear: both;
}

And on your page (or in a script file):

var is_supported = false
  , val = '';
if (window.getComputedStyle) {
  val = window.getComputedStyle(document.body, null).getPropertyValue('clear');
} else if (document.body.currentStyle) {
  val = document.body.currentStyle.clear;
}

if (val == 'both') {
  is_supported = true;
}

Final Code

Hacky or not, what I’ve tried to show here is a reusable way to detect a relatively modern browser and to prevent older browsers from applying styles or running scripts, causing them to display only the HTML of your page. The code required is quite minimal and it should allow you to concentrate your efforts on building wonderful things for more modern browsers using more modern techniques without worry.

Although you may not need everything I’ve presented here, putting all the pieces together gives us this:

<head>
  <link rel="stylesheet" href="mq-test.css"
        media="only screen and (min-resolution: 0.1dpcm)">
  <link rel="stylesheet" href="mq-test.css"
        media="only screen and (-webkit-min-device-pixel-ratio:0)
        and (min-color-index:0)">
  <link rel="stylesheet" href="mq-test.css"
        media="only screen and (-webkit-min-device-pixel-ratio:1.1)">

  <!--[if IE 8]>
  <link rel="stylesheet" href="mq-test.css">
  <![endif]-->

  <script>
    if (navigator.userAgent.indexOf('UCBrowser') > -1) {
      var link  = document.createElement('link');
      link.rel  = 'stylesheet';
      link.href = 'mq-test.css';
      document.getElementsByTagName('head')[0].appendChild(link);
    }
  </script>
</head>
<body>
<!-- content here... -->
  <script>
    var is_supported = false
      , val = '';
    if (window.getComputedStyle) {
      val = window.getComputedStyle(document.body, null).getPropertyValue('clear');
    } else if (document.body.currentStyle) {
      val = document.body.currentStyle.clear;
    }

    if (val == 'both') {
      is_supported = true;
    }

    if (is_supported) {
      // Load or run JavaScript for supported browsers here.
    }
  </script>
</body>

I’ve made another test page with all of the extras.

Credits

I couldn’t have done this without the use of BrowserStack, Can I Use and the work of the folks at Browserhacks and Jeff Clayton, so thanks to all those involved, and please let me know if you have any thoughts or feeback.

  • WebReflection

    I think you have a typo: AFAIK `getComputedStyle` is the one that accepts a second argument, not `getPropertyValue`. That second argument can be a pseudo element, so that instead of adding pointless random styles to the body that could change the page layout, you can use elements which style is completely pointless and even use a pseudo element. For instance, you could use `title:before { color: #BADA55; }` and then verify that `if ((function(s,p,t){return s(t,’before’)[p](‘color’)!=s(t,null)[p](‘color’)}(getComputedStyle,’getPropertyValue’,document.querySelector(‘title’)))) supported=true;` … I mean, I got you don’t care much about using “hacks” (and I agree) but when it comes to flags you should really try to stick with unobtrusive checks. Using something that messes with floats, as example, when you could have some external script in your page, doesn’t feel like a good move ;-)

    • Andy Kirk

      Hi WebReflection, thanks for your feedback. You’re right, the `null` argument is in the wrong place. Not sure how that happened, I’ll see about getting that changed.

      The `body{clear:both}` declaration was just a simple illustration, it wasn’t meant be definitive. You could check for any property on any element declared in your stylesheet to prove it was loaded. Having said that, I can’t think of a way that the `clear` option could actually cause any problems. Do you have any examples?
      I don’t think using a pseudo is an possibility, though. I’m pretty sure it’s value can’t be obtained in IE8 and I wanted to offer that as an option in this case.

    • Andy Kirk

      Hi WebReflection, thanks for your feedback. You’re right, the `null` argument is in the wrong place. Not sure how that happened, I’ll see about getting that changed.

      The `body{clear:both}` declaration was just a simple illustration, it wasn’t meant be definitive. You could check for any property on any element declared in your stylesheet to prove it was loaded. Having said that, I can’t think of a way that the `clear` option could actually cause any problems. Do you have any examples?
      I don’t think using a pseudo is an possibility, though. I’m pretty sure it’s value can’t be obtained in IE8 and I wanted to offer that as an option in this case.

      • WebReflection

        regarding IE8, you already use conditional comments. Whatever is inside that, is specific for IE8 and then supported there but you are right about getComputedStyle unable to get that so my point was: don’t use any CSS value on any visible element so that the browser has nothing to do in terms of rendering. The specific clear thing is just a layout related property that might have side-effects. Why do you want to risk side-effects when you can use the title or any other ignored element to obtain the same feature? ;-)

        • Andy Kirk

          Sure, I understand what you’re saying. I was just trying to keep the example as simple and reusable as possible, and not fragment the JS.

          As I say, you can test on any _real_ style you declare in the stylesheet you’re loading, it doesn’t really matter which it is, what’s important is detecting _if_ the stylesheet has loaded or not. For example, if in your design you have a div with an id of ‘wrapper’ that has background of `#eee`, you can test for that, but that example is much more subjective and verbose.

          I can’t think of a specific case where the `clear` on the `body` would _actually_ have side-effects though, and it made sense to me to provide a reusable example. But since I can’t be 100% sure there isn’t something I hadn’t thought of, I put in the proviso. I couldn’t think of another non-default property setting that wouldn’t have any impact on the display but could be detected by JS. I’m sure there are some I haven’t thought of :-)

          • WebReflection

            look at this page, for instance https://m.flickr.com/#/photos/britishlibrary/sets/ … you see what a library could do? Put a div before the head and the body, within the HTML. It’s hazard to use clear or layout modifiers on the wild but feel free to hazard.

          • Andy Kirk

            Hi, Sorry, I only just noticed this reply.
            Adding `clear: both;` to the body element of that page doesn’t break anything, though I can sort of see your point. However it’s up to the individual / team implementing this to be be responsible with their HTML and as I’ve said before, you can test for anything you’re actually setting in your stylesheet. Said implementers could simply pick something else to test on.

  • Andy Kirk

    Hi WebReflection, thanks for your feedback. You’re right, the `null` argument is in the wrong place. Not sure how that happened, I’ll see about getting that changed.

    The `body{clear:both}` declaration was just a simple illustration, it wasn’t meant be definitive. You could check for any property on any element declared in your stylesheet to prove it was loaded. Having said that, I can’t think of a way that the `clear` option could actually cause any problems. Do you have any examples?
    I don’t think using a pseudo is an possibility, though. I’m pretty sure it’s value can’t be obtained in IE8 and I wanted to offer that as an option in this case.

  • Arno

    Nice find, thank you for this post!

    • http://careersreport.com Julie Berry

      I need to show excellent online freelancing opportunity… 3 to 5 h of work /a day… Weekly paycheck… Extra bonus for job well done…Earnings of $6k-$9k /for month… Just few h of spare time, a pc, elementary knowledge of internet and~ dependable internet-connection needed…Click on my disqus~~~~page to learn more

  • https://twitter.com/nickautomatic Nick F

    Interesting idea. One thing to bear in mind is that you don’t necessarily have to provide *no* styles at all to browsers that don’t “cut the mustard”: using this technique, you could still provide basic typography, colour, maybe even some simple layout, etc, in CSS that all browsers receive, and then save more sophisticated layout, animation, etc, for the browsers that cut the mustard. That way you can still take advantage of this technique without necessarily denying old browsers all styling.

    • Andy Kirk

      Hi Nick, thanks for the reply.

      I’ve thought about this a lot and yes, you can do that but it comes with it’s own set of challenges.

      Firstly:
      Either you’d have to find a way to load the ‘basic’ stylesheet for ‘all but’ the modern browsers supported by this technique (perhaps by using `not` in the media queries – I’m not sure, I haven’t tried it)…

      OR
      You’d have to construct your CSS in such a way that the ‘modern’ stylesheet only adds to the ‘basic’ one to avoid duplication. This can lead to some awkwardness in trying to organise your CSS and means there will be two http requests, so I’d recommend avoiding this approach.

      Secondly (and this is the big one), you’d have to test this ‘basic’ stylesheet in every browser to make sure it was doing what you wanted, especially if you’re attempting any sort of layout.

      This was what I attempted to do first, but I discovered that even very simple layouts would break in very old browsers. I found this frustrating and I wanted way to avoid ever having to test in these old browsers ever again.

      This is what led me to this technique in the first place – it’s a way of cutting support for the very old browsers – drawing a line in the sand and saying “No more!”.

      Providing them with a ‘basic’ stylesheet essentially undermines the whole point of this technique :-)

      Thanks,
      Andy

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in Front-end, once a week, for free.