What's going on here?
Building CSS mobile-first is the way forward, because blah blah blah progressive enhancement blah. Problem is, Internet Explorer prior to 9 ignores anything within media query blocks, leaving those browsers with mobile styles.
Not all of us can get away with that, but thankfully, as Chris Eppstein points out, Sass 3.2+ can be used to generate a separate stylesheet with everything it needs to create a "desktop" look.
This page was built mobile-first where smaller width devices get a single column layout, but IE8 and below still get a two column layout. The code is on github, but read on for a brief explanation.
If you prefer to use Less, Colin Bacon details a similar approach.
_layout.scss
This file controls the page layout:
.page {
max-width: 70em;
margin: 0 auto;
}
.content-margins {
margin: 10px;
@include respond-min(60em) {
margin: 20px;
}
@include respond-min(75em) {
margin: 40px;
}
}
.primary {
@include respond-min(45em) {
float: left;
width: 70%;
@include old-ie {
// These hacks won't appear in the normal stylesheet
display: inline;
zoom: 1;
whatever-ie-needs: le-sigh;
}
& .content-margins {
margin-right: 30px;
}
}
}
.secondary {
@include respond-min(45em) {
float: left;
width: 30%;
}
}
As you can see, there are some special blocks for doing responsive stuff, along with some IE specific stuff. These are user-defined mixins, defined in...
_utils.scss
$fix-mqs: false !default;
@mixin respond-min($width) {
// If we're outputting for a fixed media query set...
@if $fix-mqs {
// ...and if we should apply these rules...
@if $fix-mqs >= $width {
// ...output the content the user gave us.
@content;
}
}
@else {
// Otherwise, output it using a regular media query
@media screen and (min-width: $width) {
@content;
}
}
}
// I also have a respond-max mixin, that does what you might expect
$old-ie: false !default;
@mixin old-ie {
// Only use this content if we're dealing with old IE
@if $old-ie {
@content;
}
}
all.scss
This simply brings all the includes together.
@import 'utils';
@import 'global';
@import 'layout';
all-old-ie.scss
This generates the IE<9 stylesheet:
$old-ie: true;
$fix-mqs: 65em;
@import 'all';
Here we set the width we want our media queries to be flattened for. In this case, we want to act like the document is 65em wide. This means our media queries for greater-than 45em & 60em apply, but the one for 75em is simply discarded.
Including the CSS
To give the right CSS to the right browsers, we use good old conditional comments:
<!--[if lte IE 8]>
<link rel="stylesheet" href="css/all-old-ie.css">
<![endif]-->
<!--[if gt IE 8]><!-->
<link rel="stylesheet" href="css/all.css">
<!--<![endif]-->
Other solutions
There are other ways to cater for IE that don't depend on Sass, such as this (documented by Jeremy Keith) and respond.js.
Jeremy's method works but depends on you putting your responsive stuff in a separate file. Personally I find that putting overrides far away from the thing it's overriding makes it difficult to maintain, which is also why I'm against separately-maintained CSS files for IE hacks.
respond.js is obviously JavaScript dependant, and requires you to host the CSS on the same server as the page, meaning you can't use a performance enhancing CDN. Also, I hear it's a little slow, which JS-CSS crawlers generally are, but I don't have any evidence particular to respond.js.
Won't this bloat my CSS?
Each call to respond-min/max
will result in an
@media
block in your CSS, whereas you may optimise this
to one block per breakpoint if you were hand-writing your CSS.
In that case, the Sass output will indeed be larger than hand-written CSS, but gzip will chew through the repetition making the difference negligible. Also, the Sass community is looking into ways around the repetition.
So, there you go!